C++ Konstante durch Zeiger geändert, oder doch? [duplicate]

Lesezeit: 10 Minuten

Benutzer-Avatar
Grabantot

In c ist es möglich, const mit Zeigern wie folgt zu ändern:

//mainc.c
#include <stdio.h>

int main(int argc, char** argv) {
    const int i = 5;
    const int *cpi = &i;

    printf("  5:\n");
    printf("%d\n", &i);
    printf("%d\n", i);
    printf("%d\n", cpi);    
    printf("%d\n", *cpi);   

    *((int*)cpi) = 8;
    printf("  8?:\n");
    printf("%d\n", &i);
    printf("%d\n", i);
    printf("%d\n", cpi);
    printf("%d\n", *cpi);
}

Die Konstante wird geändert, wie in der Ausgabe zu sehen ist:
Mainc-Ausgang

Wenn wir dasselbe in c++ versuchen:

//main.cpp
#include <iostream>

using std::cout;
using std::endl;

int main(int argc, char** argv) {
    const int i = 5;
    const int *cpi = &i;

    cout << "  5:" << '\n';
    cout << &i << '\n';
    cout << i << '\n';
    cout << cpi << '\n';    
    cout << *cpi << '\n';   

    *((int*)cpi) = 8;
    cout << "  8?:" << '\n';
    cout << &i << '\n';
    cout << i << '\n';
    cout << cpi << '\n';
    cout << *cpi << '\n';

    int* addr = (int*)0x28ff24;
    cout << *addr << '\n';
}

Das Ergebnis ist nicht so eindeutig:
Hauptausgang

Von der Ausgabe sieht es so aus i ist immer noch 5 und befindet sich immer noch in 0x28ff24 also bleibt const unverändert. Aber gleichzeitig cpi ist auch 0x28ff24 (das Gleiche wie &i), aber der Wert, auf den es zeigt, ist 8 (nicht 5).

Kann mir bitte jemand erklären, welche Art von Magie hier passiert?

Hier erklärt: https://stackoverflow.com/a/41098196/2277240

  • Undefiniertes Verhalten, nehme ich an.

    – Edgar Rokjan

    12. Dezember 2016 um 8:25 Uhr

  • Obwohl es aus sprachlicher Sicht ein undefiniertes Verhalten ist, können wir erahnen, was in g++ passiert ist. Ich denke, dass, wenn Sie verwenden i direkt verwendet der Compiler stattdessen den Wert 5. An Stellen, an denen Sie über Zeiger auf die Variable zugreifen, erhalten Sie den geänderten Wert. Bei einigen anderen Architekturen können Konstanten jedoch in einem nicht beschreibbaren Speicher platziert werden, und ein Versuch, dort zu schreiben, kann von der Hardware ignoriert werden oder andere Fehler verursachen.

    – Marian

    12. Dezember 2016 um 8:45 Uhr

  • Ich schwöre, es gibt ein kanonisches Duplikat für diese FAQ, aber ich kann es nicht finden.

    – Ludin

    12. Dezember 2016 um 8:47 Uhr

  • Übrigens, printf("%d\n", &i); und printf("%d\n", cpi); beide rufen undefiniertes Verhalten auf, weil Sie den falschen Formatbezeichner verwenden.

    Benutzer1084944

    12. Dezember 2016 um 9:51 Uhr


  • @Lundin Dieser? Warum kann ich den Inhalt von const char *ptr ändern?

    – Dmitri Grigorjew

    12. Dezember 2016 um 10:32 Uhr


Benutzer-Avatar
Bathseba

Das Verhalten beim Wegwerfen const aus einer Variablen (sogar über einen Zeiger oder eine Referenz in C++), die ursprünglich als deklariert wurde constund anschließend versucht, die Variable über diesen Zeiger oder Verweis zu ändern, ist nicht definiert.

Also ändern i wenn es als deklariert ist const int i = 5; ist undefiniertes Verhalten: Die Ausgabe, die Sie beobachten, ist eine Manifestation davon.

  • Dann entweder &i oder *cpi muss lügen und es sieht nicht so aus, als wäre es letzteres. Sollte ich niemals der Adresse einer Konstante vertrauen? Ist es eine Dummy-Adresse?

    – Grabantot

    12. Dezember 2016 um 8:30 Uhr


  • Überpedantisch sein, wegwerfen const ist in Ordnung, aber das Objekt tatsächlich zu modifizieren ist UB. Dieser Fall kann in generischem Code auftreten.

    – QUentin

    12. Dezember 2016 um 8:31 Uhr

  • @Quentin: Das ist eine gute Sache, darauf hinzuweisen. Vielen Dank.

    – Bathseba

    12. Dezember 2016 um 8:34 Uhr

  • @grabantot: Wenn Ihr Programm undefiniertes Verhalten aufruft, können Sie nicht vertrauen überhaupt nichts. Sie können nicht einmal Dingen vertrauen, von denen Sie glauben, dass sie passiert sind, „bevor“ undefiniertes Verhalten aufgerufen wird!

    Benutzer1084944

    12. Dezember 2016 um 9:47 Uhr


  • @grabantot Undefiniertes Verhalten bedeutet, dass buchstäblich alles passieren kann. Der Compiler muss nicht einmal konsequent bei seiner Definition von “irgendetwas” sein! Nicht nur in dieser Codezeile wird jedes Programm, das einmal undefiniertes Verhalten aufruft, den gesamten Lauf vergiften. Der Grund dafür besteht darin, Implementierungen zu ermöglichen, das zu tun, was “am billigsten” ist, in Fällen, die niemals passieren sollten, und so Optimierungen zu ermöglichen. Verändern Sie niemals eine ursprünglich als const deklarierte Variable!

    – Muzer

    12. Dezember 2016 um 10:14 Uhr

Es ist ein undefiniertes Verhalten gemäß C11 6.7.3/6:

Wenn versucht wird, ein Objekt zu ändern, das mit einem konstant qualifizierten Typ definiert ist, indem ein lvalue mit einem nicht konstant qualifizierten Typ verwendet wird, ist das Verhalten undefiniert.

(C++ wird einen ähnlichen normativen Text haben.)

Und da es sich um undefiniertes Verhalten handelt, kann alles passieren. Einschließlich: seltsame Ausgabe, Programm stürzt ab, “scheint gut zu funktionieren” (dieser Build).

Benutzer-Avatar
Yves

Die Regel von const_cast<Type *>() oder c-Typ-Umwandlung (Type *):
Die Konvertierung ist zu entfernen const Erklärung, die NICHT zu entfernen const des Wertes (Objekt) selbst.

const Type i = 1;
// p is a variable, i is an object
const Type * p = &i; // i is const --- const is the property of i, you can't remove it
(Type *)p; // remove the const of p, instead the const of i ---- Here p is non-const but i is ALWAYS const!

Wenn Sie nun versuchen, den Wert von zu ändern i durch pes ist undefiniertes Verhalten, weil i ist IMMER konstant.

Wann wird diese Art der Konvertierung verwendet?
1) Wenn Sie sicherstellen können, dass der angegebene Wert NICHT konstant ist.
z.B

int j = 1;
const int *p = &j;
*(int *)p = 2; // You can change the value of j because j is NOT const

2) Der angezeigte Wert ist konstant, aber Sie lesen ihn NUR und ändern ihn NIEMALS.

Wenn Sie wirklich einen konstanten Wert ändern müssen, entwerfen Sie Ihren Code bitte neu, um diese Art von Fall zu vermeiden.

  • @MM Danke. Tatsächlich kopiere ich nur die Sätze aus der Programmiersprache C++, aber mein Beispiel ist nicht geeignet. Ich werde meine Antwort überarbeiten.

    – Yves

    12. Dezember 2016 um 15:00 Uhr

Benutzer-Avatar
Grabantot

Nach einigem Nachdenken schätze ich, dass ich weiß, was hier passiert. Obwohl es architektur-/implementierungsabhängig ist, da es sich um ein undefiniertes Verhalten handelt, wie Marian betonte. Mein Setup ist mingw 5.x 32bit auf Windows 7 64bit, falls es jemanden interessiert.

C++-Konstanten verhalten sich wie #defines, g++ ersetzt alle i Verweise mit seinem Wert in kompiliertem Code (da i eine Konstante ist), aber es schreibt auch 5 (i-Wert) an eine Adresse im Speicher, um Zugriff darauf zu gewähren i Via-Zeiger (ein Dummy-Zeiger). Und ersetzt alle Vorkommen von &i mit dieser Adresse (nicht genau der Compiler tut es, aber Sie wissen, was ich meine).

In C werden Konstanten meist wie gewöhnliche Variablen behandelt. Mit dem einzigen Unterschied, dass der Compiler es nicht erlaubt, sie direkt zu ändern.

Deshalb sagt Bjarne Stroustrup in seinem Buch, dass man in C++ keine #defines braucht.

Hier kommt der Beweis:
Geben Sie hier die Bildbeschreibung ein

Benutzer-Avatar
TL

Es ist ein Verstoß gegen die strenge Aliasing-Regel (der Compiler geht davon aus, dass zwei Zeiger unterschiedlichen Typs niemals auf denselben Speicherort verweisen) in Kombination mit der Compileroptimierung (der Compiler führt den zweiten Speicherzugriff zum Lesen nicht aus i verwendet aber die vorherige Variable).

BEARBEITEN (wie in den Kommentaren vorgeschlagen):

Aus dem Arbeitsentwurf des ISO-C++-Standards (N3376):

“Wenn ein Programm versucht, auf den gespeicherten Wert eines Objekts über einen anderen glvalue als einen der folgenden Typen zuzugreifen, ist das Verhalten undefiniert […]
— eine cv-qualifizierte Version des dynamischen Typs des Objekts, […]
— ein Typ, der der Typ mit oder ohne Vorzeichen ist, der einer CV-qualifizierten Version des dynamischen Typs des Objekts entspricht, […]
— ein Typ, der ein (möglicherweise CV-qualifizierter) Basisklassentyp des dynamischen Typs des Objekts ist,”

Soweit ich verstehe, gibt es an, dass ein möglicherweise CV-qualifizierter Typ als Alias ​​verwendet werden kann, aber nicht, dass ein nicht CV-qualifizierter Typ für einen CV-qualifizierten Typ sein kann.

  • Die strikte Aliasing-Regel bezieht sich auf Variablen unterschiedlicher Grundtypen. Dieses Problem hat nichts mit striktem Aliasing zu tun.

    – Ralf Tandetzky

    12. Dezember 2016 um 8:36 Uhr

  • Soweit ich das (3.10) verstehe, ist es genau ein striktes Aliasing. Dort heißt es soweit ich verstehe, dass ein möglicherweise CV-qualifizierter Typ als Alias ​​verwendet werden kann, aber nicht, dass ein nicht CV-qualifizierter Typ für einen CV-qualifizierten Typ stehen kann.

    – TL

    12. Dezember 2016 um 8:47 Uhr

  • Ich neige dazu, dem zuzustimmen, obwohl die Schwierigkeit, diese Frage umfassend zu beantworten, nicht dadurch verbessert wird, dass sowohl C als auch C++ berücksichtigt werden müssen. Vielleicht @tsp, könnten Sie anfangen, einige Ihrer Antwortkommentare in die Antwort selbst aufzunehmen.

    – Bathseba

    12. Dezember 2016 um 8:54 Uhr

  • Ich habe den Standard genauer studiert und merke, dass ich mich geirrt habe. Das tut mir sehr leid. 🙁

    – Ralf Tandetzky

    12. Dezember 2016 um 9:09 Uhr

  • Ich habe eine triviale Änderung vorgenommen, um Downvotern die Möglichkeit zu geben, sich zurückzuziehen.

    – Bathseba

    12. Dezember 2016 um 9:25 Uhr

Es wäre fruchtbarer zu fragen, was ein bestimmter Compiler mit bestimmten gesetzten Flags mit diesem Code macht, als was „C“ oder „C++“ tun, da weder C noch C++ irgendetwas konsistent mit solchem ​​Code tun werden. Es ist undefiniertes Verhalten. Alles könnte passieren.

Es wäre zum Beispiel völlig legal zu kleben const Variablen in einer schreibgeschützten Speicherseite, die einen Hardwarefehler verursachen, wenn das Programm versucht, darauf zu schreiben. Oder stillschweigend scheitern, wenn Sie versuchen, darauf zu schreiben. Oder um einen dereferenzierten zu machen int* gegossen aus a const int* in eine temporäre Kopie, die geändert werden kann, ohne das Original zu beeinträchtigen. Oder jede Referenz auf diese Variable nach der Neuzuweisung zu ändern. Oder um den Code unter der Annahme umzugestalten, dass a const Die Variable kann sich nicht ändern, sodass die Operationen in einer anderen Reihenfolge ausgeführt werden, und Sie am Ende die Variable ändern, bevor Sie denken, dass Sie sie geändert haben, oder danach nicht geändert haben. Oder machen i ein Alias ​​für andere Verweise auf die Konstante 1 und ändern Sie diese auch an anderer Stelle im Programm. Oder um eine Programminvariante zu brechen, die das Programm auf völlig unvorhersehbare Weise abstürzen lässt. Oder eine Fehlermeldung ausgeben und das Kompilieren stoppen, wenn ein solcher Fehler auftritt. Oder dass das Verhalten von der Mondphase abhängt. Oder irgendetwas anderes.

Es gibt Kombinationen von Compilern und Flags und Zielen, die diese Dinge tun, mit dem möglich Ausnahme des Mondphasenfehlers. Die lustigste Variante, von der ich gehört habe, ist jedoch, dass Sie in einigen Versionen von Fortran die Konstante 1 gleich -1 setzen könnten und alle Schleifen rückwärts laufen würden.

Das Schreiben von Produktionscode wie diesem ist eine schreckliche Idee, da Ihr Compiler mit ziemlicher Sicherheit keine Garantie dafür gibt, was dieser Code in Ihrem nächsten Build tun wird.

  • Die strikte Aliasing-Regel bezieht sich auf Variablen unterschiedlicher Grundtypen. Dieses Problem hat nichts mit striktem Aliasing zu tun.

    – Ralf Tandetzky

    12. Dezember 2016 um 8:36 Uhr

  • Soweit ich das (3.10) verstehe, ist es genau ein striktes Aliasing. Dort heißt es soweit ich verstehe, dass ein möglicherweise CV-qualifizierter Typ als Alias ​​verwendet werden kann, aber nicht, dass ein nicht CV-qualifizierter Typ für einen CV-qualifizierten Typ stehen kann.

    – TL

    12. Dezember 2016 um 8:47 Uhr

  • Ich neige dazu, dem zuzustimmen, obwohl die Schwierigkeit, diese Frage umfassend zu beantworten, nicht dadurch verbessert wird, dass sowohl C als auch C++ berücksichtigt werden müssen. Vielleicht @tsp, könnten Sie anfangen, einige Ihrer Antwortkommentare in die Antwort selbst aufzunehmen.

    – Bathseba

    12. Dezember 2016 um 8:54 Uhr

  • Ich habe den Standard genauer studiert und merke, dass ich mich geirrt habe. Das tut mir sehr leid. 🙁

    – Ralf Tandetzky

    12. Dezember 2016 um 9:09 Uhr

  • Ich habe eine triviale Änderung vorgenommen, um Downvotern die Möglichkeit zu geben, sich zurückzuziehen.

    – Bathseba

    12. Dezember 2016 um 9:25 Uhr

Benutzer-Avatar
PMar

Die kurze Antwort lautet, dass C++-‘const’-Deklarationsregeln es ermöglichen, den konstanten Wert direkt an Stellen zu verwenden, an denen C die Variable dereferenzieren müsste. Dh C++ kompiliert die Anweisung

cout << i << '\n';

als ob es das wäre, was tatsächlich geschrieben wurde

cout << 5 << '\n';

Alle anderen Nicht-Zeiger-Werte sind die Ergebnisse der Dereferenzierung von Zeigern.

1143820cookie-checkC++ Konstante durch Zeiger geändert, oder doch? [duplicate]

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

Privacy policy