Ich habe diese sehr einfache Testfunktion, die ich verwende, um herauszufinden, was mit const Qualifier los ist.
int test(const int* dummy)
{
*dummy = 1;
return 0;
}
Dieser wirft mir einen Fehler mit GCC 4.8.3. Doch dieser kompiliert:
int test(const int* dummy)
{
*(char*)dummy = 1;
return 0;
}
Es scheint also, als ob der const-Qualifizierer nur funktioniert, wenn ich das Argument verwende, ohne in einen anderen Typ umzuwandeln.
Kürzlich habe ich Codes gesehen, die verwendet wurden
test(const void* vpointer, ...)
Zumindest für mich, wenn ich void* verwendet habe, neige ich dazu, es zu wirken verkohlen* für Zeigerarithmetik in Stacks oder zum Tracing. Wie kann Konstante ungültig* verhindern, dass Unterprogrammfunktionen die Daten ändern, bei denen vZeiger zeigt?
const int *var;
const
ist ein Vertrag. Durch Erhalt einer const int *
-Parameter “teilen” Sie dem Aufrufer, dass Sie (die aufgerufene Funktion) die Objekte, auf die der Zeiger zeigt, nicht ändern werden.
Ihr zweites Beispiel explizit bricht diesen Vertrag durch Wegwerfen des const-Qualifizierers und anschließendes Modifizieren des Objekts, auf das der empfangene Zeiger zeigt. Mach das niemals.
Dieser “Vertrag” wird vom Compiler erzwungen. *dummy = 1
wird nicht kompilieren. Der Cast ist eine Möglichkeit, dies zu umgehen, indem er dem Compiler mitteilt, dass Sie wirklich wissen, was Sie tun, und es Sie tun lässt. Leider ist das „Ich weiß wirklich, was ich tue“ meistens nicht der Fall.
const
kann auch vom Compiler verwendet werden, um eine Optimierung durchzuführen, die sonst nicht möglich wäre.
Hinweis zu undefiniertem Verhalten:
Bitte beachten Sie, dass während die Besetzung selbst technisch legal ist, das Ändern eines deklarierten Werts als const
ist undefiniertes Verhalten. Technisch gesehen ist die ursprüngliche Funktion also in Ordnung, solange der an sie übergebene Zeiger auf als veränderlich deklarierte Daten zeigt. Andernfalls handelt es sich um undefiniertes Verhalten.
mehr dazu am Ende des Beitrags
Was Motivation und Nutzen angeht, nehmen wir die Argumente von strcpy
und memcpy
Funktionen:
char* strcpy( char* dest, const char* src );
void* memcpy( void* dest, const void* src, std::size_t count );
strcpy
arbeitet mit Zeichenketten, memcpy
arbeitet mit generischen Daten. Während ich strcpy als Beispiel verwende, ist die folgende Diskussion für beide genau gleich, aber mit char *
und const char *
zum strcpy
und void *
und const void *
zum memcpy
:
dest
ist char *
weil im Puffer dest
Die Funktion legt die Kopie ab. Die Funktion ändert den Inhalt dieses Puffers, ist also nicht konstant.
src
ist const char *
weil die Funktion nur den Inhalt des Puffers liest src
. Es ändert es nicht.
Nur durch einen Blick auf die Deklaration der Funktion kann ein Aufrufer alle oben genannten Behauptungen aufstellen. Nach Vertrag strcpy
ändert den Inhalt des zweiten als Argument übergebenen Puffers nicht.
const
und void
sind orthogonal. Das ist die ganze Diskussion darüber const
gilt für jeden Typ (int
, char
, void
…)
void *
wird in C für “generische” Daten verwendet.
Noch mehr zu undefiniertem Verhalten:
Fall 1:
int a = 24;
const int *cp_a = &a; // mutabale to const is perfectly legal. This is in effect
// a constant view (reference) into a mutable object
*(int *)cp_a = 10; // Legal, because the object referenced (a)
// is declared as mutable
Fall 2:
const int cb = 42;
const int *cp_cb = &cb;
*(int *)cp_cb = 10; // Undefined Behavior.
// the write into a const object (cb here) is illegal.
Ich habe mit diesen Beispielen begonnen, weil sie einfacher zu verstehen sind. Von hier aus gibt es nur noch einen Schritt zu Funktionsargumenten:
void foo(const int *cp) {
*(int *)cp = 10; // Legal in case 1. Undefined Behavior in case 2
}
Fall 1:
int a = 0;
foo(&a); // the write inside foo is legal
Fall 2:
int const b = 0;
foo(&b); // the write inside foo causes Undefined Behavior
Ich muss noch einmal betonen: Wenn Sie nicht wirklich wissen, was Sie tun, und alle Menschen, die derzeit und in Zukunft am Code arbeiten, Experten sind und dies verstehen, und Sie eine gute Motivation haben, es sei denn, alle oben genannten Punkte sind erfüllt, Werfen Sie niemals die Konstanz weg!!
int test(const int* dummy)
{
*(char*)dummy = 1;
return 0;
}
Nein, das funktioniert nicht. Beständigkeit wegwerfen (mit wahrhaftig const
Daten) ist undefiniertes Verhalten und Ihr Programm wird wahrscheinlich abstürzen, wenn zum Beispiel die Implementierung gesetzt wird const
Daten im ROM. Die Tatsache, dass “es funktioniert”, ändert nichts an der Tatsache, dass Ihr Code schlecht formatiert ist.
Zumindest für mich, wenn ich void* verwendet habe, neige ich dazu, es für Zeigerarithmetik in Stapeln oder zum Tracing in char* umzuwandeln. Wie kann const void* verhindern, dass Unterprogrammfunktionen die Daten ändern, auf die vpointer zeigt?
EIN const void*
bedeutet einen Zeiger auf einige Daten, die nicht geändert werden können. Um es zu lesen, ja, müssen Sie es auf konkrete Typen wie umwandeln char
. Aber ich sagte lesennicht Schreibenwas wiederum UB ist.
Dies wird ausführlicher behandelt hier. C ermöglicht es Ihnen, die Typsicherheit vollständig zu umgehen: Es ist Ihre Aufgabe, dies zu verhindern.
Es ist möglich, dass ein bestimmter Compiler auf einem bestimmten Betriebssystem einige davon ablegen kann const
Daten in Nur-Lese-Speicherseiten. Wenn dies der Fall ist, würde der Versuch, an diesen Speicherort zu schreiben, in der Hardware fehlschlagen und beispielsweise eine allgemeine Schutzverletzung verursachen.
Das const
Qualifizierer bedeutet nur, dass Schreiben vorhanden ist undefiniertes Verhalten. Dies bedeutet, dass der Sprachstandard es dem Programm erlaubt, abzustürzen, wenn Sie dies tun (oder irgendetwas anderes). Trotzdem lässt C Sie sich selbst in den Fuß schießen, wenn Sie denken, Sie wissen, was Sie tun.
Sie können eine Subroutine nicht daran hindern, die Bits, die Sie ihr geben, so neu zu interpretieren, wie sie es möchte, und alle gewünschten Maschinenbefehle darauf auszuführen. Die aufgerufene Bibliotheksfunktion könnte sogar in Assembler geschrieben sein. Aber tun Sie das zu a const
Zeiger ist undefiniertes Verhaltenund Sie möchten wirklich nicht aufrufen undefiniertes Verhalten.
Aus dem Kopf heraus ein seltenes Beispiel, wo es sinnvoll sein könnte: Angenommen, Sie haben eine Bibliothek, die Handle-Parameter weitergibt. Wie werden sie generiert und verwendet? Intern können sie Zeiger auf Datenstrukturen sein. Das ist also eine Anwendung, wo Sie könnten typedef const void* my_handle;
Der Compiler gibt also einen Fehler aus, wenn Ihre Clients versuchen, ihn zu dereferenzieren oder versehentlich Arithmetik darauf auszuführen, und wandelt ihn dann in einen Zeiger auf Ihre Datenstruktur innerhalb Ihrer Bibliotheksfunktionen zurück. Es ist nicht die sicherste Implementierung, und Sie sollten sich vor Angreifern in Acht nehmen, die beliebige Werte an Ihre Bibliothek übergeben können, aber es ist sehr wenig Overhead.
const T*
bedeutet, dass die Daten wies auf durch den Zeiger ist const. Der 2. ist sehr zerbrechlich, da er den const wegwirft.– Oberst Zweiunddreißig
17. Januar 2016 um 18:50 Uhr
@ColonelThirtyTwo Aber nach dem, was ich in anderen Stackoverflow-Posts gelesen habe, wird const void* nicht oft verwendet, um zu verhindern, dass die Daten geändert werden? Aber es scheint mir, dass es durch Gießen sehr leicht modifiziert werden kann.
– MoneyBall
17. Januar 2016 um 18:52 Uhr
@MoneyBall ja, aber Casting ist so. Aus diesem Grund ist das Casting gefährlich – ich kann Ihr “const int * ” nehmen und es in “int64_t * ” und SIGSVF umwandeln oder andere Daten beschädigen. Das Problem liegt bei der Gießen nicht die const-Deklarationen.
– Móż
17. Januar 2016 um 23:29 Uhr
@Mσᶎ Richtig, dachte ich konst Qualifizierer könnte verhindern, dass Daten geändert werden. Es stellt sich heraus, dass Sie es in C umwandeln und ändern können, ohne dass Fehler auftreten.
– MoneyBall
18. Januar 2016 um 0:22 Uhr
Entschuldigen Sie meine Formulierung, aber …. wie dumm wäre es, wenn
const
würde funktionieren, wenn Sie etwas in einen nicht konstanten Typ umwandeln? Stellen Sie sich vor, Sie haben einen bestimmten Prototyp für einen Rückruf oder etwas, das für interne Zwecke das Argument const hat. Aber falls Sie wissen, dass IHRE Implementierung niemals ein const-Argument liefern wird ODER Sie in der Lage sind, den Fall zu erkennen, wenn dies der Fall ist, WARUM sollte der Compiler, wenn Sie per Cast explizit sagen, dass er nicht als konstant behandelt werden muss, ihn trotzdem als konstant behandeln?– dhein
18. Januar 2016 um 8:41 Uhr