C++-Funktion, die ohne Objektinitialisierung aufgerufen wird

Lesezeit: 7 Minuten

Benutzer-Avatar
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?

  • 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 ist std::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 die this 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

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.

Benutzer-Avatar
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?

  • Ich habe *a absichtlich nicht initialisiert. Der Punkt war, dass ich den Wert von ‘num’ in foo() setzen kann, ohne *a! zu initialisieren!

    – Apoorva Iyer

    19. März 2011 um 6:20 Uhr

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.

  • Ich bezweifle, dass der Wert Müll ist. Wenn ich den Wert von num in foo von num = 5 auf num = ändere irgendeine Nummerbekomme ich diese Nummer am Ausgang.

    – Apoorva Iyer

    19. März 2011 um 6:18 Uhr

  • sicherlich ist Müll – Indem Sie den Wert von num schreiben, überschreiben Sie die Adresse von etwas anderem in der Nähe im Speicher.

    – justin

    19. März 2011 um 6:20 Uhr

  • Apoorva Iyer, Sie verstehen offensichtlich das Konzept des „undefinierten Verhaltens“ nicht.

    – Titanköder

    19. März 2011 um 6:20 Uhr

  • Ach so. Also überschreibe ich eine andere Mülladresse! Ich dachte, Sie meinen, dass der Wert von num Müll ist. Das ist sinnvoller. Danke!

    – Apoorva Iyer

    19. März 2011 um 6:22 Uhr

  • Außerdem ist es der Wert, den a durch fehlende Zuweisung „erwirbt“, der Müll ist (speziell). darüber hinaus greifen Sie entweder auf eine gültige oder eine ungültige Speicheradresse zu (== Absturz oder sehr mysteriöses Verhalten).

    – justin

    19. März 2011 um 6:22 Uhr

Benutzer-Avatar
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.

Benutzer-Avatar
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;
}

Benutzer-Avatar
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 numaber GCC gibt eine Warnung aus, weil Sie das Objekt nicht explizit instanziiert haben.

Benutzer-Avatar
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 5und 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.

1012690cookie-checkC++-Funktion, die ohne Objektinitialisierung aufgerufen wird

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy