wake-up-neo.net

Warum erhalte ich die Fehlermeldung "Von Zeiger auf Ganzzahl unterschiedlicher Größe konvertieren"?

Die folgende Zeile (pure c) kompiliert sauber auf windows (win7 64 Bits + Codeblocks 13 + mingw32) und debian (Wheezy 32 Bits + Codeblocks 10 + gcc), gibt jedoch eine Warnung auf kali aus. (64 Bits + Codeblöcke + Gcc). Irgendwelche Kommentare? Ich meine, warum erhalte ich diese Warnung, obwohl dieselbe Zeile ohne Warnung zu Windows & Debian kompiliert wird?

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    unsigned int offset = (unsigned int) dst % blkLen; // warning here!
    // some code cont...
}

Die Nachricht in Codeblocks lautet: " error: cast vom Zeiger auf eine Ganzzahl unterschiedlicher Größe [-Werror = Pointer-to-int-cast]"

hinweis: Meine Compiler-Optionen sind -std=c99 -Werror -save-temps (auf allen drei Systemen gleich).

edit 2: Obwohl es mir gelungen ist, es ohne Warnung unter Verwendung der Präprozessorzeilen unten kompilieren zu lassen, hat @ Keith Thompson (siehe unten) einen entscheidenden Punkt bezüglich des Problems. Daher ist meine letzte Entscheidung, uintptr_t zu verwenden, die bessere Wahl.

edit 1: Vielen Dank für alle Antworten. Wie alle Antworten festhalten, ist das Problem ein 32-Bit-Problem. Ich habe folgende Präprozessorzeilen eingefügt:

#if __linux__   //  or #if __GNUC__
    #if __x86_64__ || __ppc64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#else
    #if _WIN32
        #define ENVIRONMENT32
    #else
        #define ENVIRONMENT64
    #endif
#endif // __linux__

#ifdef ENVIRONMENT64
    #define MAX_BLOCK_SIZE unsigned long long int
#else
    #define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64

und ersetzte dann die Problemzeile als:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

Nun scheint alles in Ordnung zu sein.

12
ssd

Der Grund für die Warnung ist, dass der Compiler den Verdacht hat, dass Sie versuchen, einen Zeiger durch int und zurück zu bewegen. Dies war vor dem Aufkommen von 64-Bit-Maschinen üblich und ist weder sicher noch vernünftig. Natürlich kann der Compiler hier deutlich erkennen, dass Sie dies nicht tun, und es wäre schön, wenn es klug genug wäre, die Warnung in solchen Fällen zu vermeiden, aber das ist nicht der Fall.

Eine saubere Alternative, die die Warnung vermeidet, und ein anderes, viel schlechteres Problem eines falschen Ergebnisses, wenn der konvertierte Wert negativ ist, ist:

unsigned int offset = (uintptr_t) dst % blkLen;

Sie müssen stdint.h oder inttypes.h angeben, damit uintptr_t verfügbar ist.

19
R..

Das Problem ist, dass das Konvertieren eines void*-Zeigers in unsigned int von Natur aus nicht portierbar ist.

Der mögliche Größenunterschied ist nur ein Teil des Problems. Dieser Teil des Problems kann mit uintptr_t gelöst werden, einem in <stdint.h> und <inttypes.h> definierten Typ. uintptr_t ist garantiert so breit, dass beim Konvertieren eines void* in uintptr_t und wieder zurück der ursprüngliche Zeigerwert (oder mindestens ein Zeigerwert, der dem ursprünglichen Wert entspricht) erzielt wird. Es gibt auch einen Typ intptr_t, der signiert ist. In der Regel sind unsignierte Typen für solche Dinge sinnvoller. Es wird nicht garantiert, dass uintptr_t und intptr_t vorhanden sind, sie sollten jedoch in einer Implementierung (C99 oder höher) mit den entsprechenden Ganzzahltypen vorhanden sein.

Aber selbst wenn Sie einen Integer-Typ haben, der groß genug ist, um einen konvertierten Zeiger zu enthalten, ist das Ergebnis nicht unbedingt für etwas anderes als das Konvertieren in einen Zeiger von Bedeutung.

Der C-Standard besagt in einer nicht normativen Fußnote:

Die Zuordnungsfunktionen zum Konvertieren eines Zeigers in eine Ganzzahl oder eine Ganzzahl auf einen Zeiger soll mit der Adressierung konsistent sein Struktur der Ausführungsumgebung.

dies ist nicht hilfreich, wenn Sie nicht wissen, was diese Adressierungsstruktur ist.

Sie scheinen zu ermitteln, wie der Versatz des void*-Arguments relativ zum nächst niedrigeren Vielfachen von blkLen ist. Mit anderen Worten, Sie versuchen zu bestimmen, wie der Zeigerwert in Bezug auf blkLen- große Speicherblöcke Ausrichten ist.

Wenn Sie wissen, dass dies sinnvoll ist, auf dem System, das Sie verwenden, zu tun, ist das in Ordnung. Sie sollten jedoch wissen, dass arithmetische Operationen für Ganzzahlen, die aus Zeigerkonvertierungen resultieren, immer noch nicht portierbar sind.

Ein konkretes Beispiel: Ich habe an Systemen (Cray-Vektor-Maschinen) gearbeitet, bei denen ein void*-Zeiger eine 64-Bit-Maschinenadresse ist (die auf ein 64-Bit-Word verweist), wobei ein 3-Bit-Byte-Offset von der Software in das Ansonsten eingefügt wird unbenutzte hochrangige 3 Bits. Beim Konvertieren eines Zeigers in eine Ganzzahl wurde die Darstellung einfach kopiert. Jede ganzzahlige Arithmetik für eine solche ganze Zahl führt wahrscheinlich zu bedeutungslosen Ergebnissen, es sei denn, diese (zugegebenermaßen exotische) Darstellung wird berücksichtigt.

Schlussfolgerungen:

  1. Sie sollten auf jeden Fall uintptr_t verwenden, anstatt Preprozessor-Tricks zu spielen, um zu bestimmen, welchen Integer-Typ Sie verwenden können. Der Implementierer Ihres Compilers hat bereits einen Integer-Typ ermittelt, der einen konvertierten Zeigerwert sicher enthalten kann. Dieses Rad muss nicht neu erfunden werden. (Achtung: <stdint.h> wurde durch den ISO-Standard von 1999 zu C hinzugefügt. Wenn Sie einen alten Compiler verwenden, der ihn nicht implementiert, müssen Sie möglicherweise noch irgendeine Art von #ifdef-Hacks verwenden. Aber ich würde trotzdem empfehlen, uintptr_t zu verwenden.) Falls verfügbar, können Sie __STDC_VERSION__ >= 199901L testen, um die C99-Konformität zu testen - obwohl einige Compiler <stdint.h> unterstützen können, ohne C99 vollständig zu unterstützen.)

  2. Sie müssen wissen, dass das Konvertieren eines Zeigers in eine Ganzzahl und das Spielen mit seinem Wert nicht portierbar ist. Das heißt nicht, dass Sie es nicht tun sollten. Eine der größten Stärken von C ist die Fähigkeit, non-portable -Code zu unterstützen, wenn Sie das brauchen.

10
Keith Thompson

Denn das Umwandeln eines void * in einen unsigned int ist genau das, was diese Warnung abfangen soll, weil sie unsicher ist. Der Zeiger kann 64-Bit sein und der int kann 32-Bit sein. Für jede Plattform ist sizeof(unsigned int) nicht garantiert sizeof(void *). Sie sollten stattdessen uintptr_t verwenden.

3
b4hand

Vielleicht, weil bei einer 64-Bit-Architektur ein Zeiger 64 Bit und ein Int bei nur 32 Bit lang ist?

Du solltest es versuchen 

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
    // some code cont...
}
2
Serge Ballesta

Ich denke, Sie erhalten die Warnung, weil die Größe von int von den Implementierungen abhängt, z. B. kann int 2 Byte lang sein oder 4 Byte lang. Dies könnte der Grund für die Warnung sein (bitte korrigieren Sie mich, wenn ich falsch liege). Aber warum versuchen Sie ein Modulo auf einem Zeiger zu machen?.

1
viggy22

Sie haben das Makro erstellt, aber Sie glauben nicht, dass es immer noch falsch ist. Da Ihr Zeiger in 86x und 64x OS in unsigned long long oder unsigned long int konvertiert wird, ist Ihr variabler Versatz unsigned int Versatz sollte auch in das jeweilige Makro konvertiert werden.

Oder Sie können einfach den Zeiger in long (d. H. Vorzeichenlose int in long) und Offset in long (d. H. Vorzeichenlose int in long) konvertieren.

0
newbie