Zeigerarithmetik für ungültigen Zeiger in C.

176

Wenn ein Zeiger auf einen bestimmten Typ (sagen wir int, char, float, ..) erhöht wird, wird sein Wert durch die Größe dieses Datentyps erhöht. Wenn ein voidZeiger, der auf Daten mit einer Größe xzeigt, inkrementiert wird, wie kann er dann xBytes nach vorne zeigen? Woher weiß der Compiler, dass er xden Wert des Zeigers erhöht?

Siva Sankaran
quelle
3
Die Frage klingt so, als würde davon ausgegangen, dass der Compiler (/ run-time) weiß, auf welchen Objekttyp der Zeiger gesetzt wurde, und fügt dem Zeiger seine Größe hinzu. Das ist ein völliges Missverständnis: Es kennt nur die Adresse.
PJTraill
2
"Wenn ein voidZeiger, der auf Daten mit einer Größe xzeigt, inkrementiert wird, wie kommt er dann dazu, xBytes voraus zu zeigen?" Das tut es nicht. Warum können Leute, die solche Fragen haben, sie nicht testen, bevor sie sie stellen? Weißt du, zumindest auf das Nötigste, wo sie prüfen, ob sie tatsächlich kompiliert werden, was dies nicht tut. -1, kann nicht glauben, dass dies +100 und -0 hat.
underscore_d

Antworten:

287

Endgültige Schlussfolgerung: Arithmetik auf a void*ist sowohl in C als auch in C ++ illegal .

GCC erlaubt es als Erweiterung, siehe Arithmetik ein void- und Funktionszeiger (beachten Sie, dass dieser Abschnitt Teil des Kapitels "C-Erweiterungen" des Handbuchs ist). Clang und ICC erlauben wahrscheinlich void*Arithmetik zum Zwecke der Kompatibilität mit GCC. Andere Compiler (z. B. MSVC) lassen das Rechnen nicht zu void*, und GCC verbietet es, wenn das -pedantic-errorsFlag angegeben ist oder wenn das -Werror-pointer-arithFlag angegeben ist (dieses Flag ist nützlich, wenn Ihre Codebasis auch mit MSVC kompilieren muss).

Der C-Standard spricht

Zitate stammen aus dem Entwurf n1256.

In der Standardbeschreibung der Additionsoperation heißt es:

6.5.6-2: Zusätzlich müssen entweder beide Operanden einen arithmetischen Typ haben oder ein Operand muss ein Zeiger auf einen Objekttyp sein und der andere muss einen ganzzahligen Typ haben.

Die Frage hier ist also, ob void*es sich um einen Zeiger auf einen "Objekttyp" handelt oder ob voides sich um einen "Objekttyp" handelt. Die Definition für "Objekttyp" lautet:

6.2.5.1: Typen werden in Objekttypen (Typen, die Objekte vollständig beschreiben), Funktionstypen (Typen, die Funktionen beschreiben) und unvollständige Typen (Typen, die Objekte beschreiben, denen jedoch Informationen fehlen, die zur Bestimmung ihrer Größe erforderlich sind) unterteilt.

Und der Standard definiert voidals:

6.2.5-19: Der voidTyp umfasst einen leeren Wertesatz ; Es ist ein unvollständiger Typ, der nicht abgeschlossen werden kann.

Da voides sich um einen unvollständigen Typ handelt, handelt es sich nicht um einen Objekttyp. Daher ist es kein gültiger Operand für eine Additionsoperation.

Daher können Sie keine Zeigerarithmetik für einen voidZeiger ausführen .

Anmerkungen

Ursprünglich wurde angenommen, dass void*aufgrund dieser Abschnitte des C-Standards Arithmetik zulässig ist:

6.2.5-27: Ein Zeiger auf void muss dieselben Darstellungs- und Ausrichtungsanforderungen haben wie ein Zeiger auf einen Zeichentyp.

Jedoch,

Dieselben Darstellungs- und Ausrichtungsanforderungen sollen Austauschbarkeit als Argumente für Funktionen, Rückgabewerte von Funktionen und Gewerkschaftsmitglieder implizieren.

Das bedeutet also, dass printf("%s", x)es die gleiche Bedeutung hat, ob es xeinen Typ char*oder hat void*, aber es bedeutet nicht, dass Sie mit a rechnen können void*.

Anmerkung des Herausgebers: Diese Antwort wurde bearbeitet, um die endgültige Schlussfolgerung widerzuspiegeln.

Sadeq
quelle
7
Aus dem C99-Standard: (6.5.6.2) Zusätzlich müssen entweder beide Operanden einen arithmetischen Typ haben oder ein Operand muss ein Zeiger auf einen Objekttyp sein und der andere muss einen ganzzahligen Typ haben. (6.2.5.19) Der void-Typ umfasst einen leeren Satz von Werten; Es ist ein unvollständiger Typ, der nicht abgeschlossen werden kann. Ich denke, das macht deutlich, dass void*Zeigerarithmetik nicht erlaubt ist. GCC verfügt über eine Erweiterung , die dies ermöglicht.
Job
1
Wenn Sie Ihre Antwort nicht mehr für nützlich halten, können Sie sie einfach löschen.
Café
1
Diese Antwort war nützlich, obwohl sie sich als falsch erwies, da sie einen schlüssigen Beweis dafür enthält, dass leere Zeiger nicht für die Arithmetik gedacht sind.
Ben Flynn
1
Dies ist eine gute Antwort, sie hat die richtige Schlussfolgerung und die notwendigen Zitate, aber die Leute, die zu dieser Frage kamen, kamen zu der falschen Schlussfolgerung, weil sie nicht bis zum Ende der Antwort gelesen haben. Ich habe dies bearbeitet, um es offensichtlicher zu machen.
Dietrich Epp
1
Clang und ICC erlauben keine void*Arithmetik (zumindest standardmäßig).
Sergey Podobry
61

Zeigerarithmetik ist für void*Zeiger nicht zulässig .

Job
quelle
16
+1 Zeigerarithmetik ist nur für Zeiger auf (vollständige) Objekttypen definiert . voidist ein unvollständiger Typ , der per Definition niemals vervollständigt werden kann.
Schot
1
@schot: Genau. Darüber hinaus wird die Zeigerarithmetik nur für einen Zeiger auf ein Element eines Array-Objekts definiert und nur dann, wenn das Ergebnis der Operation ein Zeiger auf ein Element in demselben Array oder eines nach dem letzten Element dieses Arrays wäre. Wenn diese Bedingungen nicht erfüllt sind, handelt es sich um undefiniertes Verhalten. (Ab C99 Standard 6.5.6.8)
Job
1
Anscheinend ist es mit gcc 7.3.0 nicht so. Der Compiler akzeptiert p + 1024, wobei p nichtig ist *. Und das Ergebnis ist das gleiche wie ((char *) p) + 1024
zzz777
@ zzz777 Es ist eine GCC-Erweiterung, siehe den Link in der Antwort mit der höchsten Bewertung.
Ruslan
19

Wirf es auf einen Zeichenzeiger und erhöhe deinen Zeiger um x Bytes vorwärts.

Ultimatives Verschlingen
quelle
4
Warum die Mühe? Verwandeln Sie es einfach in den richtigen Typ - der immer bekannt sein sollte - und erhöhen Sie diesen um 1. Das Umwandeln in char, Inkrementieren um xund anschließende Neuinterpretation des neuen Werts als ein anderer Typ ist sowohl sinnloses als auch undefiniertes Verhalten.
underscore_d
9
Wenn Sie Ihre man 3 qsortvoid qsort(void *base, size_t nmemb, size_t size, [snip])
Sortierfunktion
15

Der C-Standard erlaubt keine Arithmetik mit leeren Zeigern. Allerdings GNU C wird unter Berücksichtigung der Größe der erlaubten nichtig heißt 1.

C11-Standard §6.2.5

Absatz - 19

Der voidTyp umfasst einen leeren Wertesatz; Es ist ein unvollständiger Objekttyp , der nicht abgeschlossen werden kann.

Das folgende Programm funktioniert im GCC-Compiler einwandfrei.

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

Möglicherweise erzeugen andere Compiler einen Fehler.

msc
quelle
14

void *Genau aus diesem Grund können Sie keine Zeigerarithmetik für Typen durchführen!

Oliver Charlesworth
quelle
8

Sie müssen es in einen anderen Zeigertyp umwandeln, bevor Sie Zeigerarithmetik ausführen.

Jakob
quelle
7

Leere Zeiger können auf einen beliebigen Speicherblock verweisen. Daher weiß der Compiler nicht, wie viele Bytes inkrementiert / dekrementiert werden müssen, wenn eine Zeigerarithmetik für einen ungültigen Zeiger versucht wird. Daher müssen leere Zeiger zuerst auf einen bekannten Typ typisiert werden, bevor sie in eine Zeigerarithmetik einbezogen werden können.

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.
tchrist
quelle
-1

Der Compiler kennt den Typ Cast. Gegeben a void *x:

  • x+1fügt ein Byte hinzu x, Zeiger geht zu Bytex+1
  • (int*)x+1fügt sizeof(int)Bytes hinzu , Zeiger geht zu Bytex + sizeof(int)
  • (float*)x+1Adressbytes sizeof(float)usw.

Obwohl das erste Element nicht portierbar ist und gegen das Galateo von C / C ++ verstößt, ist es dennoch C-sprachrichtig, was bedeutet, dass es auf den meisten Compilern zu etwas kompiliert wird, für das möglicherweise ein entsprechendes Flag erforderlich ist (wie -Wpointer-arith).

Rytis
quelle
2
Althought the first item is not portable and is against the Galateo of C/C++Wahr. it is nevertheless C-language-correctFalsch. Das ist Doppeldenken! Zeigerarithmetik void *ist syntaktisch unzulässig, sollte nicht kompiliert werden und erzeugt in diesem Fall undefiniertes Verhalten. Wenn ein unachtsamer Programmierer das Kompilieren durch Deaktivieren einer Warnung durchführen kann, ist dies keine Entschuldigung.
underscore_d
@underscore_d: Ich denke, einige Compiler haben es als Erweiterung zugelassen, da es viel praktischer ist, als es umwandeln zu müssen, um beispielsweise einem Zeiger unsigned char*einen sizeofWert hinzuzufügen .
Supercat