2010-09-14 14 views
14

Ich finde, dass in dem folgenden CodeÄndern const Wert in C

const int i = 2; 
const int* ptr1= &i; 
int* ptr2 = (int*)ptr1; 
*ptr2 =3; 

i ‚s Wertänderungen 3. Snippet Was Ich mag könnte, wissen, warum ist dies erlaubt. In welchen Situationen könnte dies hilfreich sein?

+13

Weil C die perfekte Inkarnation der schwachen Typisierung ist. "Typen sind nur zum Spaß, wenn Sie den Typ eines Dinges nicht mögen, werfen Sie es einfach";) – delnan

+1

Osborns Gesetz http://www.anvari.org/fortune/Fortune_Cookies/95_osborn-s-law-variables-won- t.html – Jaydee

+2

Weil C & C++ Sie tun lassen wird, was Sie wollen, auch wenn Sie etwas wirklich, wirklich dummes tun wollen. –

Antwort

30

Es ist erlaubt, weil Sie die Konstanz von ptr1 übergangen haben, indem Sie es auf einen nicht-konstanten Zeiger geworfen haben. Deshalb können Abgüsse sehr gefährlich sein.

Beachten Sie, dass einige Compiler, wie GCC, Ihnen nicht erlauben, const Status wie folgt wegzuwerfen.

+0

+1 gute Antwort. –

+1

Beachten Sie, dass dieser Code abstürzen kann, wenn der Wert const im schreibgeschützten Speicher gespeichert wird. –

+1

Mein gcc (4.4.3) akzeptiert den Code ohne Beanstandung. Der Compiler von Codepad akzeptiert es auch (http://codepad.org/V9Oi65Cf), ebenso wie ideones (http://ideone.com/tjCq7) – pmg

12

Sie haben die Konstantengarantie durch das Spielen von Zeigertricks gebrochen. Dies ist nicht garantiert, um die ganze Zeit zu funktionieren und könnte fast jedes Verhalten abhängig von dem System/OS/Compiler, auf den Sie es werfen, aufrufen.

Tun Sie das nicht.

Oder zumindest tun Sie das nicht, wenn Sie wirklich wissen, was Sie tun, und selbst dann verstehen, dass es nicht im geringsten portabel ist.

5

const bedeutet wirklich "readonly".

Wie Sie herausgefunden haben, kann der Wert von const Objekte ändern, aber Sie müssen hinterhältige Methoden verwenden, um es zu tun. Und während Sie diese hinterhältigen Methoden verwenden, rufen Sie Undefined Behavior auf.

+1

@Konrad: Sowohl in C als auch in C++ ist es undefiniertes Verhalten, ein Objekt zu modifizieren, das in seiner Definition eine 'const'-Qualifikation enthält. Diese Objekte befinden sich möglicherweise im schreibgeschützten Speicher. –

+0

@Bart: Sie haben natürlich Recht ... irgendwie hatte ich ignoriert, dass dies eine variable Definition war. –

2

Es funktioniert, weil Sie die const Ness des Pointee explizit geworfen haben. Während ptr1 ist ein Zeiger auf ein const int, ptr2 ist ein Zeiger auf ein int, so ist es pointee änderbar.

Es gibt sehr wenige gute Gründe, dies zu tun, aber Sie können gerade einen Fall finden, wo es Codeverdopplung vermeidet. Zum Beispiel:

const char* letter_at(char* input, int position) 
{ 
    ... stuff ... 
    return &found_char; 
} 

char* editable_letter_at(char* input, int position) 
{ 
    return (char*)(letter_at(input, position)); 
} 

(Beispiel etwas verstümmelt aus dem C++ Beispiel in Punkt 3 Effective C++ 3.)

2

Wenn du gehst Konstantheit in einem C++ Programm wegzuzuwerfen, bitte einen C++ Stil verwenden Gießen:

int *ptr2 = const_cast<int*>(ptr1); 

Wenn Sie auf diese Art laufen Sie von Casting Probleme im Zusammenhang mit in (Sie werden, haben Sie immer), dann können Sie feststellen, wo es bei der Suche nach „const_cast“ viel schneller passiert, anstatt jede Kombination versuchen, unter der Sonne. Außerdem hilft es anderen, die nach dir kommen oder nicht.

Es gibt nur ein paar Situationen, in denen ich sehen könnte, dass dies hilfreich ist. Die Mehrheit von ihnen sind Eckfälle. Ich würde dies um jeden Preis vermeiden, wenn Sie in C++ entwickeln.

10

"Erlaubt" ist das Gegenteil von "verhindert", aber es ist auch das Gegenteil von "verboten". Sie haben gesehen, dass das Ändern Ihres const-Objekts nicht verhindert wird, aber das bedeutet nicht, dass es erlaubt ist.

Das Ändern eines const-Objekts ist nicht erlaubt im Sinne von "erlaubt". Das Verhalten Ihres Programms ist nicht durch den Standard definiert (siehe 6.7.3/5). Es ist einfach so, dass Sie bei Ihrer Implementierung auf diesem Lauf den Wert 3 gesehen haben. Bei einer anderen Implementierung oder an einem anderen Tag sehen Sie möglicherweise ein anderes Ergebnis.

Es ist jedoch nicht "verhindert", denn mit der Art und Weise, wie C-Casst funktioniert, ist das Erkennen eines Fehlers zur Kompilierungszeit ein Problem. Die Erkennung zur Laufzeit erfordert zusätzliche Prüfungen bei allen Speicherzugriffen. Der Standard wurde so konzipiert, dass Implementierungen nicht zu viel Aufwand erfordern.

Der Grund wegwerfen const wird überhaupt unterstützt, weil, wenn Sie einen Const-Zeiger auf ein nicht-const-Objekt haben, erlaubt die Sprache (in beiden Sinne), das Objekt zu ändern. Um dies zu tun, müssen Sie das const-Qualifikationsmerkmal loswerden. Die Folge davon ist, dass Programmierer const-Qualifizierer auch von Zeigern auf Objekte, die eigentlich const sind, verwerfen können.

Hier ist ein (leicht albern) Beispiel Code, der eine const-Qualifikation aus diesem Grund verwirft:

typedef struct { 
    const char *stringdata; 
    int refcount; 
} atom; 

// returns const, because clients aren't allowed to directly modify atoms, 
// just read them 
const atom *getAtom(const char *s) { 
    atom *a = lookup_in_global_collection_of_atoms(s); 
    if (a == 0) { 
     // error-handling omitted 
     atom *a = malloc(sizeof(atom)); 
     a->stringdata = strdup(s); 
     a->refcount = 1; 
     insert_in_global_collection_of_atoms(a); 
    } else { 
     a->refcount++; 
    } 
    return a; 
} 

// takes const, because that's what the client has 
void derefAtom(const atom *a) { 
    atom *tmp = (atom*)a; 
    --(tmp->refcount); 
    if (tmp->refcount == 0) { 
     remove_from_global_collection_of_atoms(a); 
     free(atom->stringdata); 
     free(atom); 
    } 
} 
void refAtom(const atom *a) { 
    ++(((atom*) a)->refcount); 
} 

Es ist dumm, weil ein besseres Design atom, um zukunfts zu erklären wäre Zeiger auf es völlig undurchsichtig zu machen und bieten eine Funktion für den Zugriff auf die Stringdaten. Aber C erfordert nicht, dass Sie alles einkapseln, es ermöglicht Ihnen, Zeiger auf vollständig definierte Typen zurückzugeben, und es möchte diese Art der Verwendung von const unterstützen, um eine schreibgeschützte Ansicht eines Objekts darzustellen, das "wirklich" modifizierbar ist.

2

C-Casts sagen dem Compiler, dass Sie wissen, was Sie tun, und dass Sie sicherstellen, dass alles am Ende funktioniert. Wenn Sie sie verwenden, ohne genau zu verstehen, was Sie tun, können Sie in Schwierigkeiten geraten.

In diesem Fall ist der Compiler innerhalb seiner Rechte, i in den Nur-Lese-Speicher zu setzen, so dass dieser Code beim Ausführen abstürzt. Alternativ könnte es funktionieren, wie Sie gesehen haben. Der Standard spezifiziert dies als undefiniertes Verhalten, so dass buchstäblich alles passieren kann.

C wurde ursprünglich entwickelt, um Unix zu schreiben, und gibt dem Programmierer absichtlich viel Freiheit beim Manipulieren von Daten, da es beim Schreiben von Betriebssystemen sehr nützlich ist, sehr implementierungsspezifischen Code zu schreiben, der Dinge macht, die nicht sicher sind jeden anderen Kontext. In regulären Anwendungscode sollte Casting mit Vorsicht erfolgen.

Und verwenden Sie keine C-Style-Modelle in C++. C++ hat eine eigene Familie von Umwandlungen, die im Code leicht zu suchen sind und oft mehr angeben, was der Cast tatsächlich macht. In diesem speziellen Fall würden Sie const_cast verwenden, was genau zeigt, was Sie tun und leicht zu finden ist.

2

Weil C weiß, dass Programmierer immer wissen, was sie tun und immer das Richtige tun. Sie können es sogar wie:

void* ptr2 = (int(*)(void(*)(char*), int[])) ptr1; 
(*(int*)ptr2) = 3; 

und es wird sich überhaupt nicht beschweren.

1

Die Anweisung const int i = 2; bedeutet, dass das Symbol/variable i Wert hält 2 und der Wert kann nicht geändert werden i verwendet; aber es gibt keine Garantie, dass der Wert nicht durch andere Mittel geändert werden kann (wie im Beispiel von OP).