8 Stimmen

Warum sind "long *" und "int *" im 32-Bit-Code nicht kompatibel?

Ich habe mich gefragt, warum der folgende Code nicht kompiliert:

void foo_int(int *a) { }
void foo_long(long *a) { }

int main()
{
    int i;
    long l;

    foo_long(&i);
    foo_int(&l);
}

Ich verwende GCC, und weder die Aufrufe funktionieren in C noch in C++. Da es sich um ein 32-Bit-System handelt, sind sowohl int als auch long vorzeichenbehaftete 32-Bit-Integer (was zur Compile-Zeit mit sizeof überprüft werden kann).

Der Grund, warum ich frage, ist, dass ich zwei separate Header-Dateien habe, die nicht unter meiner Kontrolle sind, und eine tut so etwas wie: typedef unsigned long u32; und die andere: typedef unsigned int uint32_t;. Die Deklarationen sind im Grunde kompatibel, außer wenn ich sie als Zeiger verwende wie im obigen Code-Schnipsel, muss ich explizit casten.

Irgendeine Idee, warum das so ist?

0 Stimmen

Gute Frage und interessante Antworten. Wahrscheinlich ist die technisch sinnvolle Lösung, eines der Header-Datei-Typdefinitionen zu ändern - obwohl dies möglicherweise aus anderen Gründen schwierig ist, wie Sie sagten.

28voto

system PAUSE Punkte 35068

Nur weil long und int zufälligerweise auf Ihrem konkreten Compiler und Hardware 32-Bit sind, bedeutet das nicht, dass sie auf jeder Art von Hardware und jedem Compiler immer beide 32-Bit sein werden.

C (und C++) wurden entwickelt, um zwischen verschiedenen Compilern und unterschiedlicher Hardware portabel zu sein.

26voto

sellibitze Punkte 26706

...weil der C++-Standard int und long als zwei verschiedene Typen definiert, unabhängig von ihrem Wertebereich, ihrer Darstellung usw. Wenn Sie mit "grundlegend kompatibel" meinen, dass sie ineinander umgewandelt werden können, dann ja.

18voto

Max Lybbert Punkte 19181

Ich sehe hier zwei unterschiedliche Fragen.

Erstens, bei modernen Architekturen kann man ziemlich sicher davon ausgehen, dass Zeiger die gleiche Größe haben (es gibt also keine near/far-Zeiger; aber Zeiger auf Elementfunktionen sind keine regulären Zeiger und können eine andere Größe haben); und auf einem 32-Bit-System beträgt diese Größe in der Regel 32 Bit. C geht sogar so weit, void* automatisch in etwas anderes umzuwandeln, denn letztendlich ist ein Zeiger nur eine Speicheradresse. Allerdings unterscheiden die Sprachdefinitionen verschiedene Zeiger als unterschiedliche Typen. Ich glaube, der Grund dafür ist, dass verschiedene Typen unterschiedliche Ausrichtungen haben können (die Regel für void* besagt, dass nichts wirklich vom Typ void sein kann, also wenn Sie einen Zeiger auf void haben, kennen Sie wahrscheinlich den korrekten Typ und implizit die richtige Ausrichtung ( siehe Anmerkung))

Zweitens, wie andere bereits erwähnt haben, sind longs und ints grundlegend unterschiedliche Typen mit eingebauten Standardkonvertierungen. Die Standards verlangen, dass longs mindestens so groß sind wie ints, aber möglicherweise größer. Auf Ihrer Architektur ist die Ausrichtung wahrscheinlich dieselbe, aber auf anderen Architekturen könnte dies ebenfalls unterschiedlich sein.

In Ihrem Fall ist es möglich, eine Notlösung zu finden, aber das ist nicht portabel. Wenn Sie nicht die richtigen Funktionsdeklarationen #include, und stattdessen einfach nur die Vorwärtsdeklaration selbst verwenden, sollten die Dinge magischerweise funktionieren, weil in Ihrem Fall longs und ints kompatibel sind (vorausgesetzt es gibt keine Vorzeichenprobleme; außerdem in C++ müssen Sie sowohl Ihre Deklarationen als auch die tatsächlichen Funktionsimplementierungen mit extern "C" versehen, damit Sie keine Verknüpfungsfehler erhalten). Bis Sie auf einen anderen Compiler, ein anderes Betriebssystem, eine andere Architektur usw. wechseln.

Zum Beispiel könnten Sie in C++ folgendes tun:

// in Datei lib.cc
#include 

extern "C" void foo_int(int* a)
{
    std::cout << "foo_int " << *a << " unter der Adresse " << a <<'\n';
}

extern "C" void foo_long(long* a)
{
    std::cout << "foo_long " << *a << " unter der Adresse " << a <<'\n';
}

// In Datei main.cc
extern "C" void foo_int(long* a);
extern "C" void foo_long(int* a);

int main()
{
    int i = 5;
    long l = 10;

    foo_long(&i);
    foo_int(&l);
}

(In C würden Sie extern "C" entfernen und anstelle von cout printf verwenden).

Verwenden Sie GCC, um wie folgt zu kompilieren:

$ g++ -c lib.cc -o lib.o
$ g++ main.cc lib.o
$ ./a.out
foo_long 5 unter der Adresse 0x22cce4
foo_int 10 unter der Adresse 0x22cce0

ANMERKUNG Da es keine Objekte vom Typ void gibt, kann ein void* nur auf Objekte eines anderen Typs zeigen. Der Compiler kannte den tatsächlichen Typ, als er das Objekt dort platzierte. Der Compiler kannte auch die Ausrichtung für diesen Typ, als er das Objekt allozierte. Sie kennen die Ausrichtung möglicherweise nicht wirklich, aber die Umwandlung ist nur garantiert, wenn sie wieder auf den ursprünglichen Typ erfolgt, in diesem Fall werden Ausrichtung und Größe gleich bleiben.

Aber es gibt Komplikationen. Zum einen muss die Speicherung des Objekts an beiden Stellen gleich sein (kein Problem bei primitiven Typen). Zum anderen ist es möglich, mit void*s auf beliebigen Speicher zu zeigen, aber Programmierer, die das tun, wissen wahrscheinlich, was sie tun.

3 Stimmen

Dies sollte die ausgewählte Antwort sein.

2 Stimmen

Es ist nicht sicher anzunehmen, dass Zeiger auf Member (in C++) die gleiche Größe haben wie andere Zeiger.

7voto

rlbond Punkte 62333

long und int sind zwei unterschiedliche Typen, auch wenn sie die gleiche Größe haben. Es würde massive Auswirkungen in der c++ Template-Welt geben, wenn sie von einigen Compilern gleich behandelt würden.

4voto

Greg Rogers Punkte 34400

Da mir keine der bisher gegebenen Antworten besonders gefällt, bin ich zum C++-Standard gegangen:

4.7 Integral-Konvertierungen [conv.integral]

1 Ein Rvalue eines Ganzzahltyps kann in ein Rvalue eines anderen Ganzzahltyps konvertiert werden. Ein Rvalue eines Aufzählungstyps kann in ein Rvalue eines Ganzzahltyps konvertiert werden.

Dies besagt, dass es erlaubt ist, eine Ganzzahl implizit in eine andere zu konvertieren, sodass die beiden Typen (da sie dieselbe Größe haben) als Rvalues austauschbar sind.

4.10 Zeigerkonvertierungen [conv.ptr]

1 Ein konstanter Integralausdruck (expr.const) Rvalue des Ganzzahltyps, der den Wert null ergibt (als Nullzeigerkonstante bezeichnet), kann in einen Zeigertyp konvertiert werden. Das Ergebnis ist ein Wert (der als Nullzeigerwert dieses Typs bezeichnet wird), der sich von jedem Zeiger auf ein Objekt oder eine Funktion unterscheidet. Zwei Nullzeigerwerte desselben Typs müssen gleich sein. Die Konvertierung einer Nullzeigerkonstante in einen Zeiger eines cv-qualifizierten Typs ist eine einzige Konvertierung und keine Sequenz einer Zeigerkonvertierung, gefolgt von einer Qualifizierungskonvertierung (conv.qual).

2 Ein Rvalue vom Typ "Zeiger auf cv T," wobei T ein Objekttyp ist, kann in ein Rvalue vom Typ "Zeiger auf cv void" konvertiert werden. Das Ergebnis der Konvertierung eines "Zeigers auf cv T" in einen "Zeiger auf cv void" zeigt auf den Beginn des Speicherorts, an dem sich das Objekt vom Typ T befindet, als ob das Objekt ein am meisten abgeleitetes Objekt (intro.object) vom Typ T ist (das heißt, kein Basisklassen-Subobjekt).

3 Ein Rvalue vom Typ "Zeiger auf cv D," wobei D ein Klasstyp ist, kann in ein Rvalue vom Typ "Zeiger auf cv B" konvertiert werden, wobei B eine Basisklasse (class.derived) von D ist. Wenn B eine nicht zugängliche (class.access) oder mehrdeutige (class.member.lookup) Basisklasse von D ist, ist ein Programm, das diese Konvertierung erfordert, fehlerhaft. Das Ergebnis der Konvertierung ist ein Zeiger auf das Basisklassen-Subobjekt des abgeleiteten Klassenobjekts. Der Wert des Nullzeigers wird in den Nullzeigerwert des Zieltyps konvertiert.

Es ist nur erlaubt, implizit zu konvertieren:

  • 0 zu jedem Zeigertyp (was ihn zu einem Nullzeiger macht)
  • jeden Zeigertyp zu void* (angemessen cv-qualifiziert)
  • abgeleiteter Zeiger zu einem Basiscb-zeiger (angemessen cv-qualifiziert)

Auch wenn der zugrunde liegende Maschinentyp der gleiche ist, ist es nicht erlaubt, zwischen den beiden Typen implizit zu konvertieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X