Warum werden Zeiger nicht standardmäßig mit NULL initialisiert?

118

Kann jemand bitte erklären, warum Zeiger nicht initialisiert sind NULL?
Beispiel:

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

Das Programm würde nicht in das if weil treten buf eintreten, es nicht null ist.

Ich würde gerne wissen, warum. In welchem ​​Fall benötigen wir eine Variable mit aktiviertem Papierkorb, insbesondere Zeiger, die den Papierkorb im Speicher adressieren?

Jonathan
quelle
13
Nun, weil grundlegende Typen nicht initialisiert werden. Ich gehe also von Ihrer "echten" Frage aus: Warum werden grundlegende Typen nicht initialisiert?
GManNickG
11
"Das Programm würde nicht in das if treten, weil buf nicht null ist". Das stimmt nicht Da Sie nicht wissen, was Buf ist , können Sie nicht wissen, was es nicht ist .
Drew Dormann
Im Gegensatz zu Java gibt C ++ dem Entwickler eine Menge mehr Verantwortung.
Rishi
Ganzzahlen, Zeiger, standardmäßig 0, wenn Sie den Konstruktor () verwenden.
Erik Aronesty
Aufgrund der Annahme, dass jemand, der C ++ verwendet, weiß, was er tut, weiß außerdem jemand, der einen rohen Zeiger über einen intelligenten Zeiger verwendet , (noch mehr), was er tut!
Lofty Lion

Antworten:

161

Wir alle wissen, dass Zeiger (und andere POD-Typen) initialisiert werden sollten.
Die Frage lautet dann: Wer sollte sie initialisieren?

Grundsätzlich gibt es zwei Methoden:

  • Der Compiler initialisiert sie.
  • Der Entwickler initialisiert sie.

Nehmen wir an, der Compiler hat alle Variablen initialisiert, die vom Entwickler nicht explizit initialisiert wurden. Dann stoßen wir auf Situationen, in denen die Initialisierung der Variablen nicht trivial war und der Entwickler dies am Deklarationspunkt nicht getan hat, weil er eine Operation ausführen und dann zuweisen musste.

Jetzt haben wir die Situation, dass der Compiler dem Code, der die Variable auf NULL initialisiert, eine zusätzliche Anweisung hinzugefügt hat. Später wird der Entwicklercode hinzugefügt, um die korrekte Initialisierung durchzuführen. Oder unter anderen Bedingungen wird die Variable möglicherweise nie verwendet. Viele C ++ - Entwickler würden unter beiden Bedingungen auf Kosten dieser zusätzlichen Anweisung schlecht schreien.

Es ist nicht nur an der Zeit. Aber auch Platz. Es gibt viele Umgebungen, in denen beide Ressourcen knapp sind und die Entwickler auch nicht aufgeben möchten.

ABER : Sie können den Effekt des Erzwingens der Initialisierung simulieren. Die meisten Compiler warnen Sie vor nicht initialisierten Variablen. Deshalb drehe ich meine Warnstufe immer auf die höchstmögliche Stufe. Weisen Sie dann den Compiler an, alle Warnungen als Fehler zu behandeln. Unter diesen Bedingungen generieren die meisten Compiler dann einen Fehler für Variablen, die nicht initialisiert, aber verwendet werden, und verhindern somit die Generierung von Code.

Martin York
quelle
5
Bob Tabor sagte: "Zu viele Leute haben nicht genug über die Initialisierung nachgedacht!" Es ist 'freundlich', alle Variablen automatisch zu initialisieren, aber es braucht Zeit und langsame Programme sind 'unfreundlich'. Eine Tabelle oder Redakteure, die den gefundenen zufälligen Müll Malloc zeigten, wären inakzeptabel. C, ein scharfes Werkzeug für geschulte Benutzer (gefährlich bei Missbrauch), sollte keine Zeit in Anspruch nehmen, um automatische Variablen zu initialisieren. Ein Trainingsrad-Makro zum Initiieren von Variablen könnte sein, aber viele denken, es sei besser, aufzustehen, aufmerksam zu sein und ein bisschen zu bluten. Zur Not arbeiten Sie so, wie Sie üben. Übe also so, wie du es haben würdest.
Bill IV
2
Sie wären überrascht, wie viele Fehler nur von jemandem vermieden würden, der die gesamte Initialisierung behebt. Dies wäre eine mühsame Arbeit, wenn es keine Compiler-Warnungen gäbe.
Jonathan Henson
4
@Loki, es fällt mir schwer, deinem Standpunkt zu folgen. Ich habe nur versucht, Ihre Antwort als hilfreich zu empfehlen. Ich hoffe, Sie haben das gesammelt. Wenn nicht, tut es mir leid.
Jonathan Henson
3
Wenn der Zeiger zuerst auf NULL und dann auf einen beliebigen Wert gesetzt wird, sollte der Compiler dies erkennen und die erste NULL-Initialisierung optimieren können, oder?
Korchkidu
1
@ Korchkidu: Manchmal. Eines der Hauptprobleme ist jedoch, dass es keine Möglichkeit gibt, Sie zu warnen, dass Sie vergessen haben, Ihre Initialisierung durchzuführen, da es nicht wissen kann, dass die Standardeinstellung nicht perfekt für Ihre Verwendung ist.
Deduplikator
41

Zitieren von Bjarne Stroustrup in TC ++ PL (Special Edition S.22):

Die Implementierung eines Features sollte Programmen, die es nicht benötigen, keinen erheblichen Overhead auferlegen.

John
quelle
und geben Sie auch nicht die Option. Es scheint
Jonathan
8
@ Jonathan nichts hindert Sie daran, den Zeiger auf null zu initialisieren - oder auf 0, wie es in C ++ Standard ist.
stefanB
8
Ja, aber Stroustrup hätte die Standard-Syntax dazu bringen können, die Programmkorrektheit anstelle der Leistung zu bevorzugen, indem der Zeiger auf Null initialisiert wurde, und der Programmierer hätte explizit verlangen müssen, dass der Zeiger nicht initialisiert wird. Schließlich bevorzugen die meisten Leute korrekt-aber-langsam gegenüber schnell-aber-falsch, da es im Allgemeinen einfacher ist, eine kleine Menge Code zu optimieren, als Fehler im gesamten Programm zu beheben. Besonders wenn ein Großteil davon von einem anständigen Compiler erledigt werden kann.
Robert Tuck
1
Die Kompatibilität wird dadurch nicht beeinträchtigt. Die Idee wurde in Verbindung mit "int * x = __uninitialized" betrachtet - Sicherheit standardmäßig, Geschwindigkeit durch Absicht.
MSalters
4
Ich mag was Dmacht. Wenn Sie keine Initialisierung wünschen, verwenden Sie diese Syntax float f = void;oder int* ptr = void;. Jetzt wird es standardmäßig initialisiert, aber wenn Sie es wirklich brauchen, können Sie den Compiler daran hindern.
Deft_code
23

Weil die Initialisierung Zeit braucht. In C ++ sollten Sie als erstes mit einer Variablen explizit initialisieren:

int * p = & some_int;

oder:

int * p = 0;

oder:

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

quelle
1
k, wenn die Initialisierung Zeit braucht und ich sie immer noch möchte, können meine Zeiger trotzdem null werden, ohne sie manuell festzulegen? Sehen Sie, nicht, weil ich es nicht korrigieren will, weil ich den Eindruck habe, dass ich niemals unitiliazed Zeiger mit Papierkorb auf ihrer Adresse verwenden werde
Jonathan
1
Sie initialisieren Klassenmitglieder im Konstruktor der Klasse - so funktioniert C ++.
3
@ Jonathan: aber null ist auch Müll. Mit einem Nullzeiger können Sie nichts Nützliches tun. Das Dereferenzieren ist ebenso ein Fehler. Erstellen Sie Zeiger mit geeigneten Werten, nicht mit Nullen.
DrPizza
2
Das Initialisieren eines Zeigers auf Nnull kann sinnvoll sein. Und es gibt verschiedene Operationen, die Sie für Nullzeiger ausführen können - Sie können sie testen und Löschen für sie aufrufen.
4
Wenn Sie einen Zeiger niemals verwenden, ohne ihn explizit zu initialisieren, spielt es keine Rolle, was er enthält, bevor Sie ihm einen Wert gegeben haben, und nach dem C- und C ++ - Prinzip, nur für das zu bezahlen, was Sie verwenden, wird dies nicht getan automatisch. Wenn es einen akzeptablen Standardwert gibt (normalerweise den Nullzeiger), sollten Sie ihn initialisieren. Sie können es initialisieren oder nicht initialisieren, wie Sie möchten.
David Thornley
20

Denn eines der Mottos von C ++ lautet:


Sie zahlen nicht für das, was Sie nicht verwenden


Aus diesem Grund prüft operator[]die vectorKlasse beispielsweise nicht, ob der Index außerhalb der Grenzen liegt.

KeatsPeeks
quelle
12

Aus historischen Gründen, hauptsächlich weil dies in C so gemacht wird, warum es in C so gemacht wird, ist eine andere Frage, aber ich denke, dass das Null-Overhead-Prinzip irgendwie in diese Entwurfsentscheidung involviert war.

AraK
quelle
Ich denke, weil C als Sprache der unteren Ebene mit einfachem Zugriff auf den Speicher (auch als Zeiger bezeichnet) betrachtet wird, gibt es Ihnen die Freiheit, das zu tun, was Sie wollen, und verursacht keinen Overhead, indem Sie alles initialisieren. Übrigens denke ich, dass es von der Plattform abhängt, da ich an einer Linux-basierten mobilen Plattform gearbeitet habe, die den gesamten Speicher vor der Verwendung auf 0 initialisiert hat, sodass alle Variablen auf 0 gesetzt werden.
stefanB
8

Außerdem haben wir eine Warnung, wenn Sie es blasen: "wird möglicherweise verwendet, bevor ein Wert zugewiesen wird" oder eine ähnliche Aussprache, abhängig von Ihrem Compiler.

Sie kompilieren mit Warnungen, richtig?

Joshua
quelle
Und es ist nur als Bestätigung möglich , dass die Compiler-Ablaufverfolgung fehlerhaft sein könnte.
Deduplikator
6

Es gibt nur wenige Situationen, in denen es jemals Sinn macht, eine Variable nicht zu initialisieren, und die Standardinitialisierung ist mit geringen Kosten verbunden. Warum also?

C ++ ist nicht C89. Zur Hölle, sogar C ist nicht C89. Sie können Deklarationen und Code mischen, daher sollten Sie die Deklaration so lange verschieben, bis Sie einen geeigneten Wert zum Initialisieren haben.

DrPizza
quelle
2
Dann muss nur jeder Wert zweimal geschrieben werden - einmal von der Setup-Routine des Compilers und erneut vom Programm des Benutzers. Normalerweise kein großes Problem, aber es summiert sich (z. B. wenn Sie ein Array von 1 Million Elementen erstellen). Wenn Sie eine automatische Initialisierung wünschen, können Sie jederzeit Ihre eigenen Typen erstellen, die dies tun. Auf diese Weise müssen Sie jedoch keinen unnötigen Overhead akzeptieren, wenn Sie dies nicht möchten.
Jeremy Friesner
3

Ein Zeiger ist nur ein anderer Typ. Wenn Sie einen oder einen anderen POD-Typ erstellen int, charwird dieser nicht auf Null initialisiert. Warum sollte ein Zeiger also? Dies kann für jemanden, der ein solches Programm schreibt, als unnötiger Aufwand angesehen werden.

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

Wenn Sie wissen, dass Sie es initialisieren werden, warum sollte das Programm Kosten verursachen, wenn Sie es zum ersten Mal pBufoben in der Methode erstellen ? Dies ist das Null-Overhead-Prinzip.

LeopardSkinPillBoxHat
quelle
1
Auf der anderen Seite könnten Sie char * pBuf = Bedingung tun? neues Zeichen [50]: m_myMember-> buf (); Es ist eher eine Frage der Syntax als der Effizienz, aber ich stimme Ihnen trotzdem zu.
the_drow
1
@the_drow: Nun, man kann es komplexer machen, nur damit ein solches Umschreiben nicht möglich ist.
Deduplikator
2

Wenn Sie einen Zeiger möchten, der immer auf NULL initialisiert ist, können Sie eine C ++ - Vorlage verwenden, um diese Funktionalität zu emulieren:

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
quelle
1
Wenn ich dies implementieren würde, würde ich mich nicht um den Kopierer oder die Zuweisung kümmern - die Standardeinstellungen sind ganz in Ordnung. Und dein Zerstörer ist sinnlos. Sie können natürlich auch Zeiger testen, indem Sie unter bestimmten Umständen den Operator "weniger als operater et al" verwenden, daher sollten Sie sie bereitstellen.
OK, weniger als trivial zu implementieren. Ich hatte den Destruktor so, dass der Speicher nicht als baumelnder Zeiger auf Müll verbleibt, wenn das Objekt außerhalb des Gültigkeitsbereichs liegt (dh lokal innerhalb eines Unterbereichs einer Funktion definiert), aber immer noch Platz auf dem Stapel beansprucht. Aber Alter im Ernst, ich habe das in weniger als 5 Minuten geschrieben. Es soll nicht perfekt sein.
Adisak
OK hat alle Vergleichsoperatoren hinzugefügt. Die Standardüberschreibungen sind möglicherweise redundant, werden jedoch hier explizit angegeben, da dies ein Beispiel ist.
Adisak
1
Ich konnte nicht verstehen, wie dies alle Zeiger auf Null setzen würde, ohne sie manuell zu setzen. Können Sie bitte erklären, was Sie hier getan haben?
Jonathan
1
@ Jonathan: Dies ist im Grunde ein "intelligenter Zeiger", der nichts anderes tut, als den Zeiger auf null zu setzen. IE statt Foo *a, verwenden Sie InitializedPointer<Foo> a- Eine rein akademische Übung, da Foo *a=0weniger tippen. Der obige Code ist jedoch aus pädagogischer Sicht sehr nützlich. Mit einer kleinen Modifikation (des "Platzhalters" ctor / dtor und der Zuweisungsoperationen) könnte es leicht auf verschiedene Arten von intelligenten Zeigern erweitert werden, einschließlich Zeigern mit Gültigkeitsbereich (die auf dem Destruktor frei sind) und Zeigern mit Referenzzählung durch Hinzufügen von inc / dec-Operationen, wenn der m_pPointer gesetzt oder gelöscht ist.
Adisak
2

Beachten Sie, dass statische Daten auf 0 initialisiert werden (sofern Sie nichts anderes angeben).

Und ja, Sie sollten Ihre Variablen immer so spät wie möglich und mit einem Anfangswert deklarieren. Code wie

int j;
char *foo;

sollte Alarmglocken auslösen, wenn Sie es lesen. Ich weiß nicht, ob Flusen dazu überredet werden können, darüber zu karpfen, da dies zu 100% legal ist.

pm100
quelle
Ist das GARANTIERT oder nur eine gängige Praxis der heutigen Compiler?
Gha.st
1
statische Variablen werden auf 0 initialisiert, was auch für Zeiger das Richtige ist (dh sie werden auf NULL gesetzt, nicht auf alle Bits 0). Dieses Verhalten wird durch den Standard garantiert.
Alok Singhal
1
Die Initialisierung statischer Daten auf Null wird durch den C- und C ++ - Standard garantiert. Dies ist nicht nur gängige Praxis
groovingandi
1
Vielleicht, weil einige Leute sicherstellen wollen, dass ihr Stapel gut ausgerichtet ist, deklarieren sie alle Variablen oben in der Funktion vor? Vielleicht schreiben sie in einem Dialekt, der dies erfordert?
KitsuneYMG
1

Ein weiterer möglicher Grund dafür ist, dass Zeiger zur Verbindungszeit eine Adresse erhalten, die indirekte Adressierung / De-Referenzierung eines Zeigers jedoch in der Verantwortung des Programmierers liegt. Normalerweise kümmert es den Compiler nicht weniger, aber die Last wird an den Programmierer weitergegeben, um die Zeiger zu verwalten und sicherzustellen, dass keine Speicherlecks auftreten.

Kurz gesagt, sie werden in dem Sinne initialisiert, dass die Zeigervariable zur Verbindungszeit eine Adresse erhält. In Ihrem obigen Beispielcode wird garantiert, dass dies abstürzt oder ein SIGSEGV generiert.

Initialisieren Sie aus Gründen der Vernunft immer Zeiger auf NULL, wenn Sie versuchen, sie ohne mallocoder zu dereferenzierennew Willen clue den Programmierer in den Grund , warum das Programm falsch verhalten hat .

Hoffe das hilft und macht Sinn,

t0mm13b
quelle
0

Nun, wenn C ++ Zeiger initialisieren würde, dann hätten die C-Leute, die sich beschweren "C ++ ist langsamer als C", etwas Reales, an dem sie sich festhalten könnten;)

Fred
quelle
Das ist nicht mein Grund. Mein Grund ist, dass wenn die Hardware 512 Bytes ROM und 128 Bytes RAM hat und ein zusätzlicher Befehl zum Nullstellen ein Zeiger sogar ein Byte ist, was einen ziemlich großen Prozentsatz des gesamten Programms ausmacht. Ich brauche das Byte!
Jerry Jeremiah
0

C ++ hat einen C-Hintergrund - und es gibt einige Gründe dafür:

C, sogar mehr als C ++ ist ein Assembler-Ersatz. Es tut nichts, was Sie ihm nicht sagen. Dafür: Wenn du es NULL machen willst - mach es!

Wenn Sie Dinge in einer Bare-Metal-Sprache wie C auf Null setzen, tauchen automatisch Konsistenzfragen auf: Wenn Sie etwas mallocieren - sollte es automatisch auf Null gesetzt werden? Was ist mit einer Struktur, die auf dem Stapel erstellt wurde? sollten alle Bytes auf Null gesetzt werden? Was ist mit globalen Variablen? was ist mit einer Aussage wie "(* 0x18);" Bedeutet das dann nicht, dass die Speicherposition 0x18 auf Null gesetzt werden sollte?

gha.st
quelle
In C können Sie tatsächlich Speicher verwenden, wenn Sie All-Zero-Speicher zuweisen möchten calloc().
David Thornley
1
nur mein Punkt - wenn Sie es tun wollen, können Sie, aber es wird nicht automatisch für Sie getan
gha.st
0

Was sind diese Hinweise, von denen Sie sprechen?

Für Ausnahme Sicherheit, immer verwenden auto_ptr, shared_ptr, weak_ptrund ihre andere Varianten.
Ein Kennzeichen für guten Code ist ein Code, der keinen einzigen Aufruf an enthält delete.

shoosh
quelle
3
Seit C ++ 11 meiden auto_ptrund ersetzen unique_ptr.
Deduplikator
-2

Oh Junge. Die eigentliche Antwort ist, dass es einfach ist, den Speicher auf Null zu setzen, was eine grundlegende Initialisierung für beispielsweise einen Zeiger darstellt. Das hat auch nichts mit der Initialisierung des Objekts selbst zu tun.

Angesichts der Warnungen, die die meisten Compiler auf höchster Ebene geben, kann ich mir nicht vorstellen, auf höchster Ebene zu programmieren und sie als Fehler zu behandeln. Da mir das Aufdrehen noch nie einen Fehler in großen Mengen an produziertem Code erspart hat, kann ich dies nicht empfehlen.

Charles Eli Käse
quelle
Wenn der Zeiger nicht erwartet wird , ist das NULLInitialisieren genauso ein Fehler.
Deduplikator