
Apoorva Iyer
Warum wird der folgende Code ausgeführt?
#include <iostream>
class A {
int num;
public:
void foo(){ num=5; std::cout<< "num="; std::cout<<num;}
};
int main() {
A* a;
a->foo();
return 0;
}
Die Ausgabe ist
num=5
Ich kompiliere dies mit gcc und erhalte nur die folgende Compiler-Warnung in Zeile 10:
(Warnung: ‘a’ wird in dieser Funktion nicht initialisiert verwendet)
Aber nach meinem Verständnis sollte dieser Code überhaupt nicht ausgeführt werden? Und wie kommt es, dass es num den Wert 5 zuweist, wenn num nicht existiert, weil noch kein Objekt vom Typ A erstellt wurde?
Der Code erzeugt undefiniertes Verhalten, weil er versucht, einen nicht initialisierten Zeiger zu dereferenzieren. Undefiniertes Verhalten ist unvorhersehbar und folgt keinerlei Logik. Aus diesem Grund machen Fragen, warum Ihr Code etwas tut oder nicht tut, keinen Sinn.
Du fragst warum es läuft? Es läuft nicht. Es produziert undefiniertes Verhalten.
Sie fragen, wie es einem nicht existierenden Mitglied 5 zuweist? Es ordnet nichts irgendetwas zu. Es produziert undefiniertes Verhalten.
Sie sagen, die Ausgabe ist 5
? Falsch. Die Ausgabe ist es nicht 5
. Es gibt keine sinnvolle Ausgabe. Der Code produziert undefiniertes Verhalten. Nur weil es irgendwie zufällig gedruckt wurde 5
in Ihrem Experiment bedeutet absolut nichts und hat keine sinnvolle Erklärung.

Mateen Ulhaq
Sie haben nicht initialisiert *a
.
Versuche dies:
#include <iostream>
class A
{
int num;
public:
void foo(){ std::cout<< "num="; num=5; std::cout<<num;}
};
int main()
{
A* a = new A();
a->foo();
return 0;
}
Wenn Zeiger nicht (richtig) initialisiert werden, kann dies zu undefiniertem Verhalten führen. Wenn Sie Glück haben, zeigt Ihr Zeiger auf eine Stelle im Heap, die zur Initialisierung ansteht*. (Vorausgesetzt, dabei wird keine Ausnahme ausgelöst.) Wenn Sie Pech haben, überschreiben Sie einen Teil des Speichers, der für andere Zwecke verwendet wird. Wenn Sie wirklich Pech haben, wird dies unbemerkt bleiben.
Dies ist kein sicherer Code; ein “Hacker” könnte es wahrscheinlich ausnutzen.
* Selbst wenn Sie auf diesen Ort zugreifen, gibt es natürlich keine Garantie, dass er später nicht “initialisiert” wird.
“Glück” (eigentlich macht es “Glück” schwieriger, Ihr Programm zu debuggen):
// uninitialized memory 0x00000042 to 0x0000004B
A* a;
// a = 0x00000042;
*a = "lalalalala";
// "Nothing" happens
“Pech” (erleichtert das Debuggen Ihres Programms, daher halte ich es nicht wirklich für “Pech”):
void* a;
// a = &main;
*a = "lalalalala";
// Not good. *Might* cause a crash.
// Perhaps someone can tell me exactly what'll happen?
A* a;
ist ein nicht initialisierter Zeiger.
Der Wert, den Sie sehen, ist Müll, und Sie haben Glück, dass Sie keinen Absturz erlitten haben.
Hier findet keine Initialisierung statt.
hier gibt es keine aufgabe.
Ihre Klasse ist einfach genug, dass ernstere Probleme nicht gezeigt werden.
A* a(0);
würde zu einem Absturz führen. ein nicht initialisierter Zeiger würde in manchen Fällen zum Absturz führen und lässt sich bei komplexeren Typen leichter reproduzieren.
dies ist die Folge des Umgangs mit nicht initialisierten Zeigern und Objekten und weist auf die Wichtigkeit von Compiler-Warnungen hin.

Nawaz
A* a;
a->foo();
Das ruft undefiniertes Verhalten. Am häufigsten stürzt das Programm ab.
Der Abschnitt §4.1/1 des C++03-Standards besagt:
Ein L-Wert (3.10) eines Nicht-Funktions-, Nicht-Array-Typs T kann in einen R-Wert konvertiert werden. Wenn T ein unvollständiger Typ ist, ist ein Programm, das diese Konvertierung erfordert, schlecht formatiert. Wenn das Objekt, auf das sich der lvalue bezieht, kein Objekt vom Typ T ist und kein Objekt eines von T abgeleiteten Typs ist, oder Wenn das Objekt nicht initialisiert ist, hat ein Programm, das diese Konvertierung erfordert, ein undefiniertes Verhalten. Wenn T ein Nicht-Klassentyp ist, ist der Typ des rvalue die cv-unqualifizierte Version von T. Andernfalls ist der Typ des rvalue T.
Siehe dieses ähnliche Thema: Wo genau sagt der C++-Standard, dass die Dereferenzierung eines nicht initialisierten Zeigers ein undefiniertes Verhalten ist?
Und wie kommt es, dass num den Wert 5 zuweist, wenn num nicht existiert, weil noch kein Objekt vom Typ A erstellt wurde.
Das nennt man Glück haben. Aber es würde nicht passieren stets.

hnhart
Das passiert meiner Meinung nach.
a->foo();
funktioniert, weil Sie gerade anrufen A::foo(a).
a
ist eine Variable vom Typ Zeiger, die sich in der Aufrufliste von main befindet. foo()
Funktion kann einen Segmentierungsfehler auslösen, wenn location a
zugegriffen wird, aber wenn nicht, dann foo()
springt einfach einige Stellen von a und überschreibt 4 Bytes Speicher mit dem Wert 5. Dann liest es denselben Wert aus.
Hab ich recht oder nicht? Bitte lassen Sie es mich wissen, ich lerne gerade über Call Stacks und würde mich über Feedback zu meiner Antwort freuen.
Sehen Sie sich auch den folgenden Code an
#include<iostream>
class A {
int num;
public:
void foo(){ num=5; std::cout<< "num="; std::cout<<num;}
};
int main() {
A* a;
std::cout<<"sizeof A is "<<sizeof(A*)<<std::endl;
std::cout<<"sizeof int is "<<sizeof(int)<<std::endl;
int buffer=44;
std::cout<<"buffer is "<<buffer<<std::endl;
a=(A*)&buffer;
a->foo();
std::cout<<"\nbuffer is "<<buffer<<std::endl;
return 0;
}

Kushal
Bei der Objekterstellung werden die Klassenmitglieder diesem bestimmten Objekt zugewiesen, auch wenn Sie das Schlüsselwort nicht verwenden new
, da das Objekt ein Zeiger auf die Klasse ist. Ihr Code läuft also gut und gibt Ihnen den Wert von num
aber GCC gibt eine Warnung aus, weil Sie das Objekt nicht explizit instanziiert haben.

Gemeinschaft
Ich verweise (hehe) Sie auf eine frühere Antwort von mir auf eine sehr ähnliche Frage: Tiny crashing program
Im Grunde überschreibst du die envs
Stack-Variable mit Ihrem Zeiger, weil Sie nicht hinzugefügt haben envs
zum main
Erklärung.
Seit envs
ist ein Array von Arrays (Strings), es ist tatsächlich sehr stark allokiert, und Sie überschreiben den ersten Zeiger in dieser Liste mit Ihrem 5
und lesen Sie es dann erneut, um damit zu drucken cout
.
Jetzt ist dies eine Antwort auf warum es passiert. Das sollten Sie offensichtlich nicht vertrauen darauf.
10126900cookie-checkC++-Funktion, die ohne Objektinitialisierung aufgerufen wirdyes
mögliches Duplikat von Wo genau sagt der C++-Standard, dass die Dereferenzierung eines nicht initialisierten Zeigers ein undefiniertes Verhalten ist?
– Nawaz
19. März 2011 um 6:30 Uhr
+1. Eine Zusatzfrage: Wenn Sie das Mitglied nicht hatten
num
darf man diese Arbeit erwarten? zB wenn es nur enthalten iststd::cout << "num=";
war also lokal staatenlos (ich frage berechtigterweise und gebe nicht nur Denkanstöße)– Merlyn Morgan-Graham
19. März 2011 um 6:39 Uhr
@Merlyn Morgan-Graham: Nein. Hier dereferenziert der Code einen nicht initialisierten Zeiger. Dies ist ein undefiniertes Verhalten (dh das Programm kann potenziell alles tun (einschließlich scheinbar funktionieren)).
– Martin York
19. März 2011 um 6:55 Uhr
@Martin: Ich denke, ich kann sehen, dass die Spezifikation dies nicht definiert, also sollten Sie sich nicht darauf verlassen, aber was könnte ein Compiler tun, der dazu führen würde, dass dies nicht funktioniert (vorausgesetzt, keine Vererbung und keine Datenmitglieder)
– Merlyn Morgan-Graham
19. März 2011 um 8:26 Uhr
@Martin: Keine Sorge, ich habe nicht vor, dies zu tun. Ich möchte einfach verstehen, in welchen Fällen der Compiler Code generieren könnte, der dazu führen könnte, dass dies bricht. Ich verstehe UB und die Dereferenzierung nicht initialisierter Daten. Dieser “Dereferenzierungsoperator” dereferenziert jedoch (notwendigerweise) nichts. Es würde nur verwendet werden, um die zu initialisieren
this
Zeiger und diethis
Zeiger wird in meinem Szenario nicht verwendet. Natürlich wird die Sekunde, in der jemand den Objektzustand hinzufügt/verwendet, unterbrochen, aber das interessiert mich nicht. Warum sollte ein C++-Compiler neben vtables einen nicht verwendeten this-Zeiger dereferenzieren?– Merlyn Morgan-Graham
20. März 2011 um 0:13 Uhr