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

7

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

%Vor%

Ich verwende GCC, und beide Aufrufe funktionieren nicht in C oder C ++. Da es sich um ein 32-Bit-System handelt, sind sowohl int als auch lang 32-Bit-Ganzzahlen mit Vorzeichen (die mit sizeof zur Kompilierungszeit überprüft werden können).

Der Grund, warum ich frage, ist, dass ich zwei separate Header-Dateien habe, die beide nicht unter meiner Kontrolle sind, und man macht so etwas wie: typedef unsigned long u32; und das andere: typedef unsigned int uint32_t; . Die Deklarationen sind grundsätzlich kompatibel, außer wenn ich sie als Zeiger verwende, wie im obigen Code-Snippet, muss ich explizit umwandeln.

Irgendeine Idee, warum das ist?

    
Greg Rogers 22.09.2009, 17:14
quelle

10 Antworten

26

Nur weil long und int auf Ihrem Compiler und Ihrer Hardware 32-Bit sind, heißt das nicht, dass sie immer 32-Bit auf jeder Hardware und jedem Compiler sind.

>

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

    
system PAUSE 22.09.2009 17:24
quelle
25

... weil der C ++ - Standard int und long zwei verschiedene Typen definiert, unabhängig von ihrem Wertebereich, ihrer Repräsentation usw. Wenn Sie mit "grundsätzlich kompatibel" meinen, dass sie miteinander konvertierbar sind, dann ja.

    
sellibitze 22.09.2009 17:16
quelle
18

Ich kann hier zwei verschiedene Fragen sehen.

Erstens ist es bei modernen Architekturen ziemlich sicher anzunehmen, dass Zeiger dieselbe Größe haben (dh es gibt keine near / far-Zeiger; Zeiger auf Member-Funktionen sind jedoch keine normalen Zeiger und können eine andere Größe haben); und auf einem 32-Bit-System beträgt diese Größe im Allgemeinen 32 Bits. C geht sogar so weit, dass void* automatisch auf irgendeinen anderen Wert angewendet wird, denn schließlich ist ein Zeiger tatsächlich nur eine Speicheradresse. Die Sprachdefinitionen unterscheiden jedoch verschiedene Zeiger als unterschiedliche Typen. Ich glaube der Grund dafür ist, dass verschiedene Typen unterschiedliche Ausrichtung haben können (die void* -Regel ist, dass nichts wirklich vom Typ void sein kann. Wenn Sie also einen Zeiger auf void haben, kennen Sie wahrscheinlich den richtigen Typ und implizit die korrekte Ausrichtung ( siehe Anmerkung ))

Zweitens sind long s und ints grundsätzlich andere Typen mit eingebauten Standard-Conversions. Die Standards verlangen, dass long s mindestens so groß ist wie int s, aber möglicherweise größer. Auf Ihrer Architektur ist die Ausrichtung wahrscheinlich die gleiche, aber auf anderen Architekturen, die auch anders sein könnten.

Es ist möglich, in Ihrem Fall zu schummeln, aber es ist nicht tragbar. Wenn du #include nicht richtig deklarierst und sie einfach weiterleitest deklariere, sollten Dinge auf magische Weise funktionieren, denn in deinem Fall sind long s und int s kompatibel (vorausgesetzt, es gibt keine Signedness-Probleme; auch in C ++ wirst du müssen extern "C" sowohl Ihre Deklarationen als auch die eigentlichen Funktionsimplementierungen, so dass Sie keine Link-Fehler bekommen). Bis Sie zu einem anderen Compiler, einem anderen Betriebssystem, einer anderen Architektur usw. wechseln.

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

%Vor% %Vor%

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

Mit GCC würden Sie wie folgt kompilieren:

%Vor%

HINWEIS Da keine Objekte vom Typ void vorhanden sind, kann ein void* nur auf Objekte eines anderen Typs zeigen. Und der Compiler kannte diesen realen Typ, als er das Objekt dorthin legte. Und der Compiler kannte die Ausrichtung für diesen Typ, wenn er das Objekt zugewiesen hat. Sie kennen die Ausrichtung möglicherweise nicht wirklich, aber die Umwandlung funktioniert nur dann, wenn sie wieder auf den ursprünglichen Typ zurückgeht. In diesem Fall sind die Ausrichtung und die Größe identisch.

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

    
Max Lybbert 22.09.2009 17:33
quelle
7

long und int sind zwei unterschiedliche Typen, auch wenn sie dieselbe Größe haben. In der C ++ - Vorlagenwelt gäbe es gewaltige Konsequenzen, wenn sie von einigen Compilern gleich behandelt würden.

    
rlbond 22.09.2009 17:17
quelle
4

Da mir eine der Antworten, die ich bisher gegeben habe, nicht besonders gefällt, ging ich zum C ++ - Standard:

  

4.7 Integrale Konvertierungen [conv.integral]

     

1 Ein rvalue eines Integer-Typs kann sein   in einen anderen Wert umgewandelt   ganzzahliger Typ Ein rvalue von einem   Aufzählungstyp kann in konvertiert werden   ein rvalue eines Integer-Typs.

Dies besagt, dass es erlaubt ist, eine ganze Zahl implizit in eine andere zu konvertieren, so dass die zwei Typen (da sie die gleiche Größe haben) als rvalues ​​austauschbar sind.

  

4.10 Zeigerkonvertierungen [conv.ptr]

     

1 Ein ganzzahliger konstanter Ausdruck   ( expr.const ) rvalue des Integer-Typs   das ergibt null (wird als null bezeichnet)   Zeigerkonstante) kann in einen Zeigertyp umgewandelt werden. Das   Ergebnis ist ein Wert (NULL genannt)   Zeigerwert dieses Typs)   Unterscheidbar von jedem Zeiger auf   ein Objekt oder eine Funktion. Zwei null   Zeigerwerte des gleichen Typs müssen   vergleiche gleich. Die Umwandlung von a   Nullzeigerkonstante auf einen Zeiger   zum cv-qualifizierten Typ ist ein Single   Umwandlung, und nicht die Folge von a   Zeigerkonvertierung gefolgt von   eine Qualifizierungsumsetzung   ( conv.qual ).

     

2 Ein rvalue vom Typ "Zeiger auf cv T"   wo T ist ein Objekttyp, kann sein   konvertiert in einen rvalue des Typs   "Zeiger auf cv void." Das Ergebnis   Konvertieren eines "Pointers zu CV T" zu   Ein "Zeiger auf cv void" zeigt auf den   Start des Speicherortes wo   das Objekt vom Typ T liegt, wie   wenn das Objekt am meisten abgeleitet ist   Objekt ( intro.object ) vom Typ T   (Dies ist kein Basisklassen-Unterobjekt).

     

3 Ein rvalue vom Typ "Zeiger auf cv D"   wo D ist ein Klassentyp, kann sein   konvertiert in einen rvalue des Typs   "Zeiger auf CV B", wobei B eine Basis ist   Klasse ( class.ederived ) von D.   Wenn B unzugänglich ist   ( class.access ) oder mehrdeutig   ( class.member.lookup ) Basisklasse von   D, ein Programm, das dies erfordert   Umwandlung ist schlecht gebildet. Das Ergebnis   der Konvertierung ist ein Zeiger auf   das Basisklassenunterobjekt des   abgeleitetes Klassenobjekt Die Null   Zeigerwert wird in den Nullwert konvertiert   Zeigerwert des Zieltyps.

Es darf nur implizit konvertiert werden:

  • 0 für jeden beliebigen Zeigertyp (als Nullzeiger)
  • jeder Zeigertyp auf void * (richtig cv-qualifiziert)
  • abgeleiteter Zeiger auf einen Basiszeiger (korrekt cv-qualifiziert)

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

    
Greg Rogers 22.09.2009 19:18
quelle
2

Dies liegt daran, dass auf manchen Plattformen long und int unterschiedlich groß sind.

%Vor%

Außerdem ist das verwirrende daran, dass Sie zwar eine Interaktion zwischen den beiden Typen durchführen müssen, aber Sie können die Funktion nicht so überladen, als ob sie wirklich zwei verschiedene Typen wären.

%Vor%     
Earlz 22.09.2009 17:41
quelle
2

int * und long * sind verschiedene Typen, die nicht unbedingt identisch sind. In jeder realen Implementierung denke ich, dass sie es sind, aber das ist weder hier noch dort für einen standardkonformen Compiler.

Ich glaube, es war einer der frühen PDP-Maschinen, in denen ein char * größer als ein int * war. Der Grund dafür war die ungerade Größe von Ints auf dieser Architektur (36Bits). Das System würde also mehrere 9bit Zeichen in ein einzelnes int packen, so dass ein char * die Adresse im Format von (int *, Offset innerhalb des int) enthielt. **

Der Standard gibt an, dass alle Zeiger als void * dargestellt werden können, und weist darauf hin, dass char * mit void * identisch sein muss. Die anderen Zeigertypen müssen jedoch nicht konvertiert werden können.

** Ich kann keine Verweise darauf finden, daher könnte die Quelle hierfür eher ein theoretisches (aber immer noch gültiges) Beispiel gewesen sein als eine tatsächliche Implementierung. C ++ FAQ Lite

    
jkerian 22.09.2009 18:08
quelle
1

int und long sind als unterschiedliche Typen definiert, so dass Sie portable programmieren können.

    
codymanix 23.09.2009 00:12
quelle
1

Ich glaube nicht, dass irgendeine dieser Antworten auf die Jagd geht.

Die Antwort lautet: Eine gültige Konvertierung zwischen Typen bedeutet keine gültige Konvertierung zwischen Zeigern. Das macht Sinn, oder? Sie möchten den folgenden Code zum kompilieren

%Vor%

Aber diesen Code kompilieren zu lassen wäre ein Rezept für ein Desaster:

%Vor%

Nur weil es eine Konvertierung zwischen long und int gibt, bedeutet das nicht, dass es eine Zeigerkonvertierung geben sollte. Aber, Sie können protestieren, long und int haben genau die gleiche Darstellung auf Ihrem Compiler! Und Sie haben Recht, aber das ändert nichts an der Tatsache, dass es sich bei Standard und Compiler um unterschiedliche Typen handelt. Und es kann nicht implizit zwischen Zeigern in Typen konvertiert werden, es sei denn, es gibt eine Vererbungsbeziehung.

Auch, während C ++ sich ändern kann, ob es nicht auf der lokalen Definition basiert, wie groß int usw. sind, ändert es nicht, ob es syntaktisch korrekt ist. Vollständig die Unterscheidung zwischen long und int wird dies tun.

    
Jack Aidley 06.03.2013 11:36
quelle
0

Sie müssen die explizite Besetzung nicht bei jedem Anruf von Hand schreiben. Ein einfaches Makro kann es tun:

%Vor%     
sambowry 23.09.2009 00:43
quelle

Tags und Links