Frage Warum bekomme ich "caste from pointer to integer von unterschiedlicher Größe" Fehler?


Die folgende Zeile (reines c) kompiliert sauber auf Fenster (Win7 64 Bits + Codeblocks 13 + mingw32) und Debian (Wheezy 32 Bits + Codeblocks 10 + gcc), aber die Warnung wird aktiviert Kali (64 Bits + Codeblocks + gcc). Irgendwelche Kommentare? Ich meine, warum bekomme ich diese Warnung, obwohl die gleiche Zeile kompiliert wird ohne irgendeine Warnung auf Windows & Debian?

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 Codeblöcken lautet:Error: Von Zeiger auf Integer anderer Größe umsetzen [-Werror = Pointer-to-Int-Cast] "

Hinweis: meine Compiler-Optionen sind -std=c99 -Werror -save-temps (Gleiches gilt für alle drei Systeme).

Edit 2: Obwohl ich es geschafft habe, es ohne Warnungen zu kompilieren, benutze die Präprozessorzeilen unten, @ Keith Thompson (siehe unten) hat einen entscheidenden Punkt zu diesem Thema. Also, meine letzte Entscheidung benutzt uintptr_t wäre eine bessere Wahl.

Bearbeiten 1: Danke für alle geantwortet. Wie alle Antworten bemerken, ist das Problem ein Problem mit 32 Bits gegenüber 64 Bits. Ich habe folgende Preprozessor-Zeilen 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 dann die Problemlinie ersetzt als:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

Jetzt scheint alles in Ordnung zu sein.


12
2017-11-07 16:14


Ursprung


Antworten:


Der Grund für die Warnung ist, dass der Compiler vermutet, dass Sie versuchen, einen Zeiger durchzulaufen int und zurück. Dies war vor dem Aufkommen von 64-Bit-Computern allgemein üblich und nicht sicher oder vernünftig. Natürlich kann der Compiler klar sehen, dass Sie das 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 es nicht.

Eine saubere Alternative, die die Warnung vermeidet, und ein noch viel ärgerlicheres Problem mit falschem Ergebnis, wenn der konvertierte Wert negativ ist:

unsigned int offset = (uintptr_t) dst % blkLen;

Sie müssen hinzufügen stdint.h oder inttypes.h haben uintptr_t verfügbar.


18
2017-11-07 16:25



Das Problem ist, dass ein void* Zeiger auf unsigned int ist von Natur aus nicht tragbar.

Der mögliche Größenunterschied ist nur ein Teil des Problems. Dieser Teil des Problems kann durch Verwendung von gelöst werden uintptr_t, ein Typ definiert in <stdint.h> und <inttypes.h>. uintptr_t ist garantiert breit genug, um ein void* zu uintptr_t und zurück wird wieder den ursprünglichen Zeigerwert ergeben (oder zumindest einen Zeigerwert, der gleich dem ursprünglichen Wert ist). Es gibt auch einen Typ intptr_t, die unterzeichnet ist; normalerweise sind vorzeichenlose Typen für diese Art von Sache sinnvoller. uintptr_t und intptr_t Es ist nicht garantiert, dass sie existieren, aber sie sollten für jede Implementierung (C99 oder höher) mit den entsprechenden Integertypen vorhanden sein.

Aber selbst wenn Sie einen Integer-Typ haben, der groß genug ist, um einen konvertierten Zeiger zu halten, ist das Ergebnis nicht unbedingt für etwas anderes bedeutungsvoll, als zurück in einen Zeiger zu konvertieren.

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

Die Mapping-Funktionen zum Konvertieren eines Zeigers in eine Ganzzahl oder ein   Ganzzahl zu einem Zeiger soll konsistent mit der Adressierung sein   Struktur der Ausführungsumgebung.

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

Sie scheinen zu bestimmen, was der Offset der void* Argument ist relativ zum nächstniedrigeren Vielfachen von blkLen; Mit anderen Worten, Sie versuchen zu bestimmen, wie der Zeigerwert ist ausgerichtet in Gedenken an blkLen-große Speicherblöcke.

Wenn Sie wissen, dass das sinnvoll ist auf dem System, das Sie verwenden, das ist gut. Sie sollten sich jedoch darüber im Klaren sein, dass arithmetische Operationen für Ganzzahlen, die aus Zeigerkonvertierungen resultieren, immer noch nicht portabel sind.

Ein konkretes Beispiel: Ich habe an Systemen (Cray-Vektor-Maschinen) gearbeitet, wo a void* Der Zeiger ist eine 64-Bit-Maschinenadresse (die auf ein 64-Bit-Wort zeigt), wobei ein 3-Bit-Byte-Offset durch Software in das ansonsten nicht verwendete eingefügt wird hoher Auftrag 3 Bits. Beim Konvertieren eines Zeigers in eine ganze Zahl wurde die Darstellung einfach kopiert. Jede Integer-Arithmetik einer solchen ganzen Zahl wird wahrscheinlich zu bedeutungslosen Ergebnissen führen, wenn sie diese (zugegebenermaßen exotische) Darstellung nicht berücksichtigt.

Schlussfolgerungen:

  1. Sie sollten auf jeden Fall verwenden uintptr_t anstatt Präprozessortricks zu spielen, um zu bestimmen, welchen Integertyp Sie verwenden können. Der Implementierer Ihres Compilers hat bereits die Aufgabe erledigt, einen Integer-Typ zu bestimmen, der einen konvertierten Zeigerwert sicher speichern kann. Es ist nicht notwendig, dieses spezielle Rad neu zu erfinden. (Vorbehalt: <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 eine Art von verwenden #ifdef Hacks. Aber ich würde immer noch vorschlagen, zu verwenden uintptr_t wenn es verfügbar ist. Sie können testen __STDC_VERSION__ >= 199901L um die C99-Konformität zu testen - obwohl einige Compiler dies unterstützen könnten <stdint.h> ohne C99 vollständig zu unterstützen.)

  2. Sie müssen beachten, dass das Konvertieren eines Zeigers in eine Ganzzahl und das Abspielen mit dem Wert nicht portabel ist. Das soll nicht heißen, dass du es nicht tun solltest; Eine der größten Stärken von C ist seine Fähigkeit zu unterstützen nicht tragbar Code, wenn das ist, was Sie brauchen.


9
2017-11-07 19:39



Weil das Gießen eines void * zu einem unsigned int ist genau das, was diese Warnung fangen soll, weil es unsicher ist. Der Zeiger kann 64-Bit sein und der int kann 32-Bit sein. Für jede gegebene Plattform, die sizeof(unsigned int) ist nicht garantiert sizeof(void *). Du solltest benutzen uintptr_t stattdessen.


3
2017-11-07 16:29



Vielleicht, weil auf einer 64-Bit-Architektur ein Zeiger 64 Bit lang und ein Int 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
2017-11-07 16:26



Ich denke, dass Sie die Warnung erhalten, weil die Größe von int von den Implementationen abhängt, zB int könnte 2 Byte lang oder 4 Byte lang sein. Dies könnte der Grund für die Warnung sein (Bitte korrigieren Sie mich, wenn ich falsch liege). Aber warum versuchen Sie immer einen Modulo auf einem Zeiger zu machen.


1
2017-11-07 16:28



Du hast das Makro gemacht, aber denkst du nicht, dass es immer noch falsch ist? Weil Ihr Zeiger in unsigned long long int oder unsigned long int konvertiert wird, die 32 Bit und 64 Bit in 86x und 64x OS sein wird, aber Ihr variabler Offset ist unsigned int, das 32bit in 64x und 86x OS ist. Also ich denke du solltest Offset auch auf das jeweilige Makro umrechnen.

Oder Sie können einfach den Zeiger auch in lang (d. H. Unsigned int in lang) und offset in long (d. H. Unsigned int in long) konvertieren.


0
2017-09-17 07:33