Zum Verständnis von ungültigen Zeigern

8

In meiner Antwort erwähne ich, dass die Dereferenzierung eines void -Zeichners eine schlechte Idee ist. Was passiert jedoch, wenn ich das tue?

%Vor%

Zusammenstellung:

%Vor%

Hier ist ein Bild von Wandbox für diejenigen, die sagen, dass es nicht passiert ist:

und eine Live-Demo in Ideone.

Es wird tatsächlich versuchen, zu lesen, was der Speicher, auf den c zeigt, hat, und dann dieses Ergebnis holen, aber am Ende tatsächlich nichts tun? Oder diese Zeile hat einfach keine Wirkung (aber dann würde GCC keine Warnung erzeugen).

Ich denke, da der Compiler nichts über den Datentyp weiß, kann er nicht viel tun, ohne die Größe des Typs zu kennen.

Warum führt das Entfenzen von void* nicht zu einem Fehler, sondern nur zu einer Warnung?

Wenn ich eine Aufgabe versuche, bekomme ich einen Fehler:

  

ungültige Verwendung von void Ausdruck

Aber sollte nicht allein die Dereferenzierung einen Fehler erzeugen?

    
gsamaras 15.09.2017, 13:42
quelle

4 Antworten

5

Ab C11 6.3.2.3 "void":

  

Der (nicht vorhandene) Wert eines void-Ausdrucks (ein Ausdruck, der den Typ void hat) darf in keiner Weise verwendet werden, und implizite oder explizite Konvertierungen (mit Ausnahme von void) sollen nicht auf einen solchen Ausdruck angewendet werden. Wenn ein Ausdruck eines anderen Typs als void-Ausdruck ausgewertet wird, wird sein Wert oder Bezeichner verworfen. (Ein ungültiger Ausdruck wird auf seine Nebenwirkungen hin bewertet.)

So kann ein Ausdruck einen void-Typ haben, Sie können nichts mit dem Ergebnis dieses Ausdrucks tun (z. B. etwas zuweisen). *c ist ein gültiger Ausdruck, der nichts tut.

6.2.5 / 19 "Typen"

  

Der Void-Typ umfasst eine leere Menge von Werten; Es ist ein unvollständiger Objekttyp, der nicht abgeschlossen werden kann.

6.5.6 / 2 "Additive Operatoren"

  

Für die Addition müssen beide Operanden einen arithmetischen Typ haben, oder ein Operand soll ein Zeiger auf einen vollständigen Objekttyp sein und der andere soll einen Integer-Typ haben.

Und Array-Subskribierung ist in Bezug auf Zeigerarithmetik definiert. Normalerweise wäre der Ausdruck &c[0] nicht erlaubt.

GCC erlaubt die Pointerarithmetik auf votortypen (und damit Subskripting-Arrays) als Erweiterung.

    
Michael Burr 15.09.2017, 14:42
quelle
12

Der C-Standard besagt explizit in 5.1.1.3p1 :

  

Eine konforme Implementierung muss mindestens eine Diagnosemeldung (in implementierungsdefinierter Weise identifiziert) erzeugen, wenn eine vorverarbeitende Übersetzungseinheit oder Übersetzungseinheit eine Verletzung einer Syntaxregel oder -bedingung enthält, selbst wenn die Das Verhalten wird auch explizit als nicht definiert oder implementierungsdefiniert angegeben. Diagnostische Nachrichten müssen unter anderen Umständen nicht erzeugt werden. 9)

Mit Fußnote 9, die das sagt

  

Die Absicht ist, dass eine Implementierung die Art der Verletzung identifizieren und wo möglich lokalisieren sollte. Natürlich kann eine Implementierung beliebig viele Diagnosen erzeugen, solange ein gültiges Programm noch korrekt übersetzt ist. Es kann auch ein ungültiges Programm erfolgreich übersetzen.

Somit entspricht GCC dem Buchstaben des C-Standards. Ihr Programm ist ein ungültiges Programm. Nur eine Diagnosemeldung ist erforderlich - und der Compiler kann Ihr ungültiges Programm erfolgreich übersetzen. Da GCC eine nicht standardmäßige Erweiterung für Zeigerarithmetik ungültig aufweist:

  

In GNU C werden Additions- und Subtraktionsoperationen auf Pointern auf void und auf Zeigern auf Funktionen unterstützt. Dies geschieht durch Behandeln der Größe eines void oder einer Funktion als 1 .

     

Eine Konsequenz daraus ist, dass sizeof auch für void und für Funktionstypen erlaubt ist und 1 zurückgibt.

     

Die Option -Wpointer-arith fordert eine Warnung an, wenn diese Erweiterungen verwendet werden.

es hat entschieden, dass es etwas "sinnvolles" mit Ihrem ungültigen Programm machen könnte und es erfolgreich übersetzt hat.

Beachten Sie, dass die nicht bewertete Dereferenzierung von pointer-to-void bereits in sizeof erforderlich ist, weil:

%Vor%

muss mit dem von

übereinstimmen %Vor%

Sie bewerten beide mit 1, so dass es einfacher ist, die verworfene Dereferenzierung von Zeigern auf void überall zuzulassen.

Wie Lundin sagt, verwenden Sie -std=c11 -pedantic-errors .

, wenn Sie tatsächliche Fehler für Constraint-Verletzungen wünschen     
Antti Haapala 15.09.2017 14:05
quelle
3

Der einzige Grund, warum das kompiliert wird, ist, weil Sie die falschen GCC-Optionen verwenden - Sie kompilieren das nicht mit einem streng übereinstimmenden C-Compiler, sondern mit GCC-Erweiterungen.

Kompiliert mit -std=c11 -pedantic-errors , erhalten wir:

%Vor%

void Zeigerdeferenzierung und Arithmetik sind gcc-Erweiterungen. Wenn solche nicht standardmäßigen Erweiterungen aktiviert sind, wird void* in Bezug auf die Arithmetik als uint8_t* behandelt, Sie können jedoch immer noch keine De-Referenzierung durchführen, da sie immer noch als unvollständiger Typ betrachtet wird.

Was GCC mit diesem Code im Nicht-Standard-Modus macht, ohne dass Optimierungen aktiviert sind (Mingw / x86), passiert nichts besonders aufregendes:

%Vor%     
Lundin 15.09.2017 14:22
quelle
2

in gcc, ist es möglich, Zeigerarithmetik auf void * -Zeiger durchzuführen ( Wie ungültige Zeigerarithmetik in GCC passiert )

Und es ist sogar möglich, sizeof(void) zu drucken, was 1 ist.

Ihr Beispiel gibt eine Warnung aus, aber die Zeile tut nichts (wie wenn Sie einen Parameter ignorieren, indem Sie (void)a ausführen, um die Warnung "unused parameter" zu vermeiden).

Versuchen Sie etwas zuzuweisen und gcc wird sich beschweren

%Vor%

gibt mir 1 Warnung und 1 Fehler

%Vor%

Oder verwenden Sie sogar eine volatile char Besetzung:

%Vor%

Sie können es also dereferenzieren, aber Sie können den dereferenzierten Wert nicht verwenden (auch versucht es zu lesen / zuzuweisen, auf keinen Fall: Sie erhalten "ungültigen Wert nicht ignoriert, wie er sein sollte")

BEARBEITEN: halbgültiges Beispiel (aus memcpy: warning: Dereferenzierung von 'void *' Zeiger )

%Vor%

Hier dereferenzieren Sie mit einem Offset , dann nehmen Sie die Adresse erneut, um c+2 zu erhalten: das funktioniert wegen der gcc Zeigerarithmetik. Natürlich:

%Vor%

ist der Weg, um die Warnung zu vermeiden und entspricht dem Standard.

    
Jean-François Fabre 15.09.2017 13:52
quelle

Tags und Links