Wann sollte const void* verwendet werden?

Lesezeit: 8 Minuten

Benutzeravatar von MoneyBall
MoneyBall

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 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

bolovs Benutzeravatar
Bolov

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!!

  • @MoneyBall Man kann mit Sicherheit sagen, dass C Ihnen Methoden gibt, um sich selbst ins Bein zu schießen (das Wegwerfen des const-Qualifikators ist eine davon). Davon abgesehen kann man davon ausgehen, dass eine Funktion den const-Qualifizierer nicht wegwirft. Es sei denn, es handelt sich um eine bösartige oder fehlerhafte Funktion.

    – Bolov

    17. Januar 2016 um 19:31 Uhr

  • @MoneyBall Ich habe einen Hinweis zum Compiler hinzugefügt, der diesen Vertrag durchsetzt

    – Bolov

    17. Januar 2016 um 19:37 Uhr

  • Sie sagten, wenn der Angerufene empfängt konst int* vZeigerdie 4 Bytes an Daten, auf die verwiesen wird vZeiger ist konstant. Also wenn konst ist ein Vertrag zwischen Anrufer und Angerufenem, wenn Angerufener angegeben ist konst Leere*, was bedeutet das? Woher weiß Angerufener, wie viele Daten konstant sind?

    – MoneyBall

    17. Januar 2016 um 19:40 Uhr


  • @DavidC.Rankin Englisch ist nicht meine Muttersprache, daher könnte ich mich in seiner Verwendung irren, aber “orthogonal” hat auch die Bedeutung von “unabhängig”. oxforddictionaries.com/definition/american_english/orthogonal , merriam-webster.com/dictionary/orthogonal

    – Bolov

    17. Januar 2016 um 20:15 Uhr

  • @bolov: FWIW, die Version mit “orthogonal” war tatsächlich besser (klarer) als die Version mit “nicht gekoppelt”. Auf jeder Website mit einer großen Anzahl von gut ausgebildeten, technisch versierten Menschen werden Sie auf viele Behauptungen darüber stoßen, wie Wörter “sollten” verwendet werden oder was sie “tatsächlich” bedeuten. Viele dieser Behauptungen sind völlig falsch; und David C. Rankins ist einer von ihnen.

    – ruach

    17. Januar 2016 um 20:38 Uhr

Benutzeravatar von edmz
edmz

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.

  • Konstanz tatsächlich wegzuwerfen ist kein undefiniertes Verhalten. Es ist wohldefiniert, sogar den Wert zu modifizieren, auf den gezeigt wird, unter der Bedingung, dass das Objekt als änderbar deklariert wurde. Siehe meine Antwort für ein detailliertes Beispiel. Aber ich stimme zu 100% zu, dass dies kein Grund ist, Konstanz wegwerfen zu lassen

    – Bolov

    17. Januar 2016 um 19:27 Uhr

  • @bolov Ja, danke für den Hinweis. Ich habe irgendwie darauf angespielt, als ich eine Implementierung erwähnte, die platziert const Daten im ROM.

    – edmz

    17. Januar 2016 um 19:37 Uhr


Benutzeravatar von Davislor
Davislor

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.

1389500cookie-checkWann sollte const void* verwendet werden?

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

Privacy policy