Pourquoi les pointeurs ne sont-ils pas initialisés avec NULL par défaut?

118

Quelqu'un peut-il expliquer pourquoi les pointeurs ne sont pas initialisés NULL?
Exemple:

  void test(){
     char *buf;
     if (!buf)
        // whatever
  }

Le programme n'entrerait pas dans le if car il bufn'est pas nul.

Je voudrais savoir pourquoi, dans quel cas avons-nous besoin d'une variable avec corbeille, spécialement des pointeurs adressant la corbeille sur la mémoire?

Jonathan
la source
13
Eh bien, parce que les types fondamentaux ne sont pas initialisés. Je suppose donc votre "vraie" question: pourquoi les types fondamentaux ne sont-ils pas initialisés?
GManNickG
11
"le programme n'entrerait pas dans le if car buf n'est pas nul". Ce n'est pas correct. Puisque vous ne savez pas ce que buf est , vous ne pouvez pas savoir ce qu'il est pas .
Drew Dormann
Contrairement à quelque chose comme Java, C ++ donne beaucoup plus de responsabilités au développeur.
Rishi
entiers, pointeurs, par défaut à 0 si vous utilisez le constructeur ().
Erik Aronesty
En raison de l'hypothèse que quelqu'un utilisant C ++ sait ce qu'il fait, de plus, quelqu'un qui utilise des pointeurs bruts sur un pointeur intelligent sait (encore plus) ce qu'il fait!
Lofty Lion

Réponses:

161

Nous savons tous que le pointeur (et les autres types de POD) doivent être initialisés.
La question devient alors «qui doit les initialiser».

Eh bien, il existe essentiellement deux méthodes:

  • Le compilateur les initialise.
  • Le développeur les initialise.

Supposons que le compilateur ait initialisé toute variable non explicitement initialisée par le développeur. Ensuite, nous nous heurtons à des situations où l'initialisation de la variable n'était pas triviale et la raison pour laquelle le développeur ne l'a pas fait au point de déclaration était qu'il / elle avait besoin d'effectuer une opération puis de l'assigner.

Nous avons donc maintenant la situation dans laquelle le compilateur a ajouté une instruction supplémentaire au code qui initialise la variable à NULL, puis plus tard, le code du développeur est ajouté pour effectuer l'initialisation correcte. Ou dans d'autres conditions, la variable n'est potentiellement jamais utilisée. Beaucoup de développeurs C ++ hurleraient dans les deux conditions au prix de cette instruction supplémentaire.

Ce n'est pas seulement une question de temps. Mais aussi de l'espace. Il y a beaucoup d'environnements où les deux ressources sont à une prime et les développeurs ne veulent pas abandonner non plus.

MAIS : vous pouvez simuler l'effet de forcer l'initialisation. La plupart des compilateurs vous avertiront des variables non initialisées. Je tourne donc toujours mon niveau d'avertissement au niveau le plus élevé possible. Dites ensuite au compilateur de traiter tous les avertissements comme des erreurs. Dans ces conditions, la plupart des compilateurs généreront alors une erreur pour les variables non initialisées mais utilisées et empêcheront ainsi la génération de code.

Martin York
la source
5
Bob Tabor a déclaré: "Trop de gens n'ont pas suffisamment réfléchi à l'initialisation!" Il est "convivial" d'initialiser toutes les variables automatiquement, mais cela prend du temps et les programmes lents sont "hostiles". Une feuille de calcul ou des éditeurs qui ont montré le malloc poubelle aléatoire trouvé serait inacceptable. C, un outil pointu pour les utilisateurs formés (dangereux s'il est mal utilisé) ne devrait pas prendre de temps à initialiser les variables automatiques. Une macro de roue d'entraînement aux variables d'initialisation pourrait être, mais beaucoup pensent qu'il vaut mieux se lever, être attentif et saigner un peu. À la rigueur, vous travaillez comme vous le pratiquez. Alors pratiquez comme vous le voudriez.
Bill IV
2
Vous seriez surpris de voir combien de bogues seraient évités rien que si quelqu'un corrigeait toute leur initialisation. Ce serait un travail fastidieux sans les avertissements du compilateur.
Jonathan Henson
4
@Loki, j'ai du mal à suivre votre point. J'essayais simplement de rendre votre réponse utile, j'espère que vous l'avez compris. Sinon, je suis désolé.
Jonathan Henson
3
Si le pointeur est d'abord défini sur NULL, puis défini sur n'importe quelle valeur, le compilateur devrait être en mesure de détecter cela et d'optimiser la première initialisation NULL, non?
Korchkidu
1
@Korchkidu: Parfois. L'un des problèmes majeurs est cependant qu'il n'a aucun moyen de vous avertir que vous avez oublié de faire votre initialisation, car il ne peut pas savoir que la valeur par défaut n'est pas parfaite pour votre utilisation.
Deduplicator
41

Citant Bjarne Stroustrup dans TC ++ PL (Special Edition p.22):

L'implémentation d'une fonctionnalité ne doit pas imposer de frais généraux importants aux programmes qui n'en ont pas besoin.

John
la source
et ne donnez pas l'option non plus. Il semble
Jonathan
8
@ Jonathan rien ne vous empêche d'initialiser le pointeur sur null - ou sur 0 comme c'est le cas en C ++.
stefanB
8
Oui, mais Stroustrup aurait pu faire en sorte que la syntaxe par défaut favorise l'exactitude du programme plutôt que les performances en initialisant à zéro le pointeur, et obliger le programmeur à demander explicitement que le pointeur ne soit pas initialisé. Après tout, la plupart des gens préfèrent le correct-mais-lent au rapide-mais-faux, au motif qu'il est généralement plus facile d'optimiser une petite quantité de code que de corriger des bogues dans l'ensemble du programme. Surtout quand une grande partie de cela peut être faite par un compilateur décent.
Robert Tuck
1
Cela ne rompt pas la compatibilité. L'idée a été considérée en conjonction avec "int * x = __uninitialized" - sécurité par défaut, vitesse par intention.
MSalters
4
J'aime ce qui Dfait. Si vous ne souhaitez pas l'initialisation, utilisez cette syntaxe float f = void;ou int* ptr = void;. Maintenant, il est initialisé par défaut, mais si vous en avez vraiment besoin, vous pouvez empêcher le compilateur de le faire.
deft_code
23

Parce que l'initialisation prend du temps. Et en C ++, la toute première chose à faire avec n'importe quelle variable est de l'initialiser explicitement:

int * p = & some_int;

ou:

int * p = 0;

ou:

class A {
   public:
     A() : p( 0 ) {}  // initialise via constructor
   private:
     int * p;
};

la source
1
k, si l'initialisation prend du temps et que je le veux toujours, est-ce que vous voulez de toute façon rendre mes pointeurs nulles sans le régler manuellement? voyez, pas parce que je ne veux pas le corriger, car il me semble que je n'utiliserai jamais des pointeurs unitiliazed avec des ordures sur leur adresse
Jonathan
1
Vous initialisez les membres de la classe dans le constructeur de la classe - c'est ainsi que fonctionne C ++.
3
@Jonathan: mais null est aussi une poubelle. Vous ne pouvez rien faire d'utile avec un pointeur nul. Déréférencer l'un est tout autant une erreur. Créez des pointeurs avec des valeurs appropriées, pas des valeurs nulles.
DrPizza
2
Initialiser un pointeur sur Nnull peut être une chose sensée à faire.Et il y a plusieurs opérations que vous pouvez effectuer sur des pointeurs nuls - vous pouvez les tester et vous pouvez appeler delete sur eux.
4
Si vous n'utilisez jamais un pointeur sans l'initialiser explicitement, peu importe ce qu'il contenait avant de lui donner une valeur, et selon le principe C et C ++ de ne payer que ce que vous utilisez, ce n'est pas fait automatiquement. S'il existe une valeur par défaut acceptable (généralement le pointeur nul), vous devez l'initialiser. Vous pouvez l'initialiser ou le laisser non initialisé, selon votre choix.
David Thornley
20

Parce que l'une des devises de C ++ est:


Vous ne payez pas pour ce que vous n'utilisez pas


Pour cette raison, le operator[]de la vectorclasse ne vérifie pas si l'index est hors limites, par exemple.

KeatsPeeks
la source
12

Pour des raisons historiques, principalement parce que c'est ainsi que cela se fait en C. Pourquoi cela se fait comme cela en C, est une autre question, mais je pense que le principe de zéro frais généraux a été impliqué d'une manière ou d'une autre dans cette décision de conception.

AraK
la source
Je suppose que C est considéré comme un langage de niveau inférieur avec un accès facile à la mémoire (aka pointeurs), donc il vous donne la liberté de faire ce que vous voulez et n'impose pas de surcharge en initialisant tout. BTW Je pense que cela dépend de la plate-forme parce que j'ai travaillé sur une plate-forme mobile basée sur Linux qui a initialisé toute sa mémoire à 0 avant utilisation afin que toutes les variables soient définies sur 0.
stefanB
8

De plus, nous avons un avertissement pour le moment où vous le faites exploser: "est peut-être utilisé avant d'attribuer une valeur" ou un verbage similaire en fonction de votre compilateur.

Vous compilez avec des avertissements, non?

Joshua
la source
Et c'est juste possible pour reconnaître que le traçage des compilateurs peut être défectueux.
Deduplicator
6

Il existe très peu de situations dans lesquelles il est logique qu'une variable ne soit pas initialisée, et l'initialisation par défaut a un petit coût, alors pourquoi le faire?

C ++ n'est pas C89. Bon sang, même C n'est pas C89. Vous pouvez mélanger les déclarations et le code, vous devez donc différer la déclaration jusqu'à ce que vous ayez une valeur appropriée pour l'initialisation.

DrPizza
la source
2
Ensuite, chaque valeur devra être écrite deux fois - une fois par la routine de configuration du compilateur, et à nouveau par le programme de l'utilisateur. Ce n'est généralement pas un gros problème, mais cela s'additionne (par exemple, si vous créez un tableau de 1 million d'éléments). Si vous voulez une initialisation automatique, vous pouvez toujours créer vos propres types qui le font; mais de cette façon, vous n'êtes pas obligé d'accepter des frais généraux inutiles si vous ne le souhaitez pas.
Jeremy Friesner
3

Un pointeur est juste un autre type. Si vous créez un int, charou tout autre type POD il n'est pas initialisés à zéro, alors pourquoi un pointeur? Cela pourrait être considéré comme une surcharge inutile pour quelqu'un qui écrit un programme comme celui-ci.

char* pBuf;
if (condition)
{
    pBuf = new char[50];
}
else
{
    pBuf = m_myMember->buf();
}

Si vous savez que vous allez l'initialiser, pourquoi le programme devrait-il engager un coût lorsque vous créez pBufpour la première fois en haut de la méthode? C'est le principe de zéro frais généraux.

LéopardPeauPillboxChapeau
la source
1
d'un autre côté, vous pouvez faire char * pBuf = condition? nouveau caractère [50]: m_myMember-> buf (); C'est plus une question de syntaxe que d'efficacité, mais je suis néanmoins d'accord avec vous.
the_drow
1
@the_drow: Eh bien, on peut le rendre plus complexe juste pour qu'une telle réécriture ne soit pas possible.
Deduplicator
2

Si vous voulez un pointeur qui est toujours initialisé à NULL, vous pouvez utiliser un modèle C ++ pour émuler cette fonctionnalité:

template<typename T> class InitializedPointer
{
public:
    typedef T       TObj;
    typedef TObj    *PObj;
protected:
    PObj        m_pPointer;

public:
    // Constructors / Destructor
    inline InitializedPointer() { m_pPointer=0; }
    inline InitializedPointer(PObj InPointer) { m_pPointer = InPointer; }
    inline InitializedPointer(const InitializedPointer& oCopy)
    { m_pPointer = oCopy.m_pPointer; }
    inline ~InitializedPointer() { m_pPointer=0; }

    inline PObj GetPointer() const  { return (m_pPointer); }
    inline void SetPointer(PObj InPtr)  { m_pPointer = InPtr; }

    // Operator Overloads
    inline InitializedPointer& operator = (PObj InPtr)
    { SetPointer(InPtr); return(*this); }
    inline InitializedPointer& operator = (const InitializedPointer& InPtr)
    { SetPointer(InPtr.m_pPointer); return(*this); }
    inline PObj operator ->() const { return (m_pPointer); }
    inline TObj &operator *() const { return (*m_pPointer); }

    inline bool operator!=(PObj pOther) const
    { return(m_pPointer!=pOther); }
    inline bool operator==(PObj pOther) const
    { return(m_pPointer==pOther); }
    inline bool operator!=(const InitializedPointer& InPtr) const
    { return(m_pPointer!=InPtr.m_pPointer); }
    inline bool operator==(const InitializedPointer& InPtr) const
    { return(m_pPointer==InPtr.m_pPointer); }

    inline bool operator<=(PObj pOther) const
    { return(m_pPointer<=pOther); }
    inline bool operator>=(PObj pOther) const
    { return(m_pPointer>=pOther); }
    inline bool operator<=(const InitializedPointer& InPtr) const
    { return(m_pPointer<=InPtr.m_pPointer); }
    inline bool operator>=(const InitializedPointer& InPtr) const
    { return(m_pPointer>=InPtr.m_pPointer); }

    inline bool operator<(PObj pOther) const
    { return(m_pPointer<pOther); }
    inline bool operator>(PObj pOther) const
    { return(m_pPointer>pOther); }
    inline bool operator<(const InitializedPointer& InPtr) const
    { return(m_pPointer<InPtr.m_pPointer); }
    inline bool operator>(const InitializedPointer& InPtr) const
    { return(m_pPointer>InPtr.m_pPointer); }
};
Adisak
la source
1
Si je mettais en œuvre cela, je ne me soucierais pas du cteur de copie ou de l'opération d'affectation - les valeurs par défaut sont tout à fait correctes. Et votre destructeur est inutile. Vous pouvez bien sûr également tester les pointeurs en utilisant l'opérateur less than et all) dans certaines circonstances), vous devez donc les fournir.
OK, moins que c'est trivial à mettre en œuvre. J'ai eu le destructeur de sorte que si l'objet sort de la portée (c'est-à-dire local défini dans une sous-portée d'une fonction) mais prend toujours de la place sur la pile, la mémoire ne soit pas laissée comme un pointeur suspendu vers les ordures. Mais mec sérieusement, j'ai écrit ça en moins de 5 min. Ce n'est pas censé être parfait.
Adisak
OK a ajouté tous les opérateurs de comparaison. Les remplacements par défaut peuvent être redondants mais ils sont ici explicitement car il s'agit d'un exemple.
Adisak
1
Je ne pouvais pas comprendre comment cela rendrait tous les pointeurs nuls sans les définir manuellement, pourriez-vous expliquer ce que vous avez fait ici s'il vous plaît?
Jonathan
1
@Jonathan: Il s'agit essentiellement d'un "pointeur intelligent" qui ne fait rien d'autre que de définir le pointeur sur null. IE au lieu de Foo *a, vous utilisez InitializedPointer<Foo> a- Un exercice purement académique car Foo *a=0c'est moins de frappe. Cependant, le code ci-dessus est très utile d'un point de vue pédagogique. Avec une petite modification (au ctor / dtor "placeholding" et aux opérations d'assignation), il pourrait être facilement étendu à divers types de pointeurs intelligents, y compris les pointeurs de portée (qui sont gratuits sur le destructeur) et les pointeurs comptés par référence en ajoutant inc / dec opérations lorsque le m_pPointer est défini ou effacé.
Adisak
2

Notez que les données statiques sont initialisées à 0 (sauf indication contraire).

Et oui, vous devez toujours déclarer vos variables le plus tard possible et avec une valeur initiale. Code comme

int j;
char *foo;

devrait déclencher l'alarme lorsque vous le lisez. Je ne sais pas si des peluches peuvent être persuadées de s'en plaindre car c'est 100% légal.

pm100
la source
Est-ce que c'est GARANTI ou simplement une pratique courante utilisée par les compilateurs d'aujourd'hui?
gha.st
1
les variables statiques sont initialisées à 0, ce qui fait aussi la bonne chose pour les pointeurs (c'est-à-dire les définit sur NULL, pas tous les bits 0). Ce comportement est garanti par la norme.
Alok Singhal
1
l'initialisation des données statiques à zéro est garantie par la norme C et C ++, ce n'est pas seulement une pratique courante
groovingandi
1
peut-être parce que certaines personnes veulent s'assurer que leur pile est bien alignée, pré-déclarent toutes les variables en haut de la fonction? Peut-être qu'ils écrivent dans un dialecte AC qui REQUIERT cela?
KitsuneYMG
1

Une autre raison possible est qu'au moment de la liaison, les pointeurs reçoivent une adresse, mais l'adressage indirect / le dé-référencement d'un pointeur est la responsabilité du programmeur. Généralement, le compilateur s'en fiche, mais le fardeau est transféré au programmeur pour gérer les pointeurs et s'assurer qu'aucune fuite de mémoire ne se produit.

En fait, en un mot, ils sont initialisés en ce sens qu'au moment de la liaison, la variable pointeur reçoit une adresse. Dans votre exemple de code ci-dessus, cela est garanti pour planter ou générer un SIGSEGV.

Pour des raisons de bon sens, initialisez toujours les pointeurs à NULL, de cette manière si une tentative de déréférencer sans mallocou new indiquera au programmeur la raison pour laquelle le programme s'est mal comporté.

J'espère que cela aide et a du sens,

t0mm13b
la source
0

Eh bien, si C ++ initialisait les pointeurs, alors les gens du C se plaignant de "C ++ est plus lent que C" auraient quelque chose de réel auquel s'accrocher;)

Fred
la source
Ce n'est pas ma raison. Ma raison est que si le matériel a 512 octets de ROM et 128 octets de RAM et une instruction supplémentaire pour mettre à zéro un pointeur est même un octet qui est un pourcentage assez élevé de l'ensemble du programme. J'ai besoin de cet octet!
Jerry Jeremiah
0

C ++ vient d'un arrière-plan C - et il y a plusieurs raisons qui en découlent:

C, encore plus que C ++, est un remplacement du langage assembleur. Il ne fait rien que vous ne lui dites pas de faire. Par conséquent: si vous voulez le NULL, faites-le!

De plus, si vous annulez des choses dans un langage bare-metal comme C, des questions de cohérence surgissent automatiquement: si vous malloc quelque chose - devrait-il être automatiquement remis à zéro? Qu'en est-il d'une structure créée sur la pile? tous les octets doivent-ils être remis à zéro? Qu'en est-il des variables globales? qu'en est-il d'une déclaration comme "(* 0x18);" cela ne signifie-t-il pas alors que la position de mémoire 0x18 doit être remise à zéro?

gha.st
la source
En fait, en C, si vous souhaitez allouer de la mémoire entièrement nulle, vous pouvez utiliser calloc().
David Thornley
1
juste mon point - si vous voulez le faire, vous pouvez, mais ce n'est pas fait pour vous automatiquement
gha.st
0

De quoi parlez-vous?

Pour plus de sécurité d'exception, utilisez toujours auto_ptr, shared_ptr, weak_ptret leurs autres variantes.
Une caractéristique d'un bon code est celle qui n'inclut pas un seul appel delete.

shoosh
la source
3
Depuis C ++ 11, évitez auto_ptret remplacez unique_ptr.
Deduplicator
-2

Oh mec. La vraie réponse est qu'il est facile de mettre à zéro la mémoire, ce qui est une initialisation de base pour, par exemple, un pointeur. Ce qui n'a rien à voir avec l'initialisation de l'objet lui-même.

Compte tenu des avertissements que la plupart des compilateurs donnent aux niveaux les plus élevés, je ne peux pas imaginer programmer au plus haut niveau et les traiter comme des erreurs. Depuis leur mise en place ne m'a jamais sauvé même un bogue dans d'énormes quantités de code produites, je ne peux pas le recommander.

Fromage Charles Eli
la source
Si l'on ne s'attend pas à ce que le pointeur soit NULL, son initialisation est tout autant une erreur.
Deduplicator