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?
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.
... 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.
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.
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:
Auch wenn der zugrunde liegende Maschinentyp derselbe ist, ist es nicht erlaubt, zwischen den beiden Typen implizit zu konvertieren.
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%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
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.