Warum ist dieser Code in der 64-Bit-Architektur fehlerfrei, funktioniert aber in der 32-Bit-Architektur einwandfrei?

112

Ich bin auf folgendes C-Puzzle gestoßen:

F: Warum ist das folgende Programm auf IA-64 fehlerfrei, funktioniert aber auf IA-32 einwandfrei?

  int main()
  {
      int* p;
      p = (int*)malloc(sizeof(int));
      *p = 10;
      return 0;
  }

Ich weiß, dass die Größe inteines 64-Bit-Computers möglicherweise nicht der Größe eines Zeigers entspricht ( int32 Bit und der Zeiger 64 Bit). Ich bin mir jedoch nicht sicher, wie dies mit dem oben genannten Programm zusammenhängt. Irgendwelche Ideen?

user7
quelle
49
Ist es so dumm, stdlib.hnicht dabei zu sein?
user786653
3
Dieser Code läuft auf meinem 64-Bit-Computer einwandfrei. Es kompiliert sogar ohne Warnungen, wenn Sie #include stdlib.h(für malloc)
mpenkov
1
D'oh! @ user786653 hat das wichtige Stück genagelt. Mit #include <stdlib.h>ist es perfekt zu finden, aber das kommt nicht in Frage.
8
@delnan - es muss jedoch nicht so funktionieren, es könnte auf einer Plattform sizeof(int) == sizeof(int*), auf der beispielsweise Zeiger über ein anderes Register als ints in der verwendeten Aufrufkonvention zurückgegeben werden, legitimerweise fehlschlagen .
Flexo
7
In einer C99-Umgebung sollte der Compiler Sie mindestens vor der impliziten Deklaration von warnen malloc(). GCC sagt: warning: incompatible implicit declaration of built-in function 'malloc'auch.
Jonathan Leffler

Antworten:

130

Die Besetzung int*maskiert die Tatsache, dass ohne den richtigen #includeRückgabetyp mallocangenommen wird int. IA-64 hat zufällig, sizeof(int) < sizeof(int*)was dieses Problem offensichtlich macht.

(Beachten Sie auch, dass es aufgrund des undefinierten Verhaltens auch auf einer Plattform, auf sizeof(int)==sizeof(int*)der true gilt, immer noch fehlschlagen kann , wenn die aufrufende Konvention andere Register für die Rückgabe von Zeigern als Ganzzahlen verwendet.)

In den häufig gestellten Fragen zu comp.lang.c wird erläutert, warum das Casting der Rückgabe von mallocniemals erforderlich und möglicherweise schlecht ist .

Flexo
quelle
5
Warum wird der Rückgabetyp von malloc ohne das richtige #include als int angenommen?
Benutzer7
11
@WTP - Dies ist ein guter Grund, immer newin C ++ zu verwenden und C immer mit einem C-Compiler und nicht mit einem C ++ - Compiler zu kompilieren.
Flexo
6
@ user7 - das sind die Regeln. Jeder Rückgabetyp wird angenommen, intwenn er nicht bekannt ist
Flexo
2
@vlad - die bessere Idee ist, immer Funktionen zu deklarieren, anstatt sich aus genau diesem Grund auf implizite Deklarationen zu verlassen. (Und nicht die Rückkehr von malloc)
Flexo
16
@ user7: "Wir haben einen Zeiger p (Größe 64), der auf 32 Bit Speicher zeigt" - falsch. Die Adresse des von malloc zugewiesenen Blocks wurde gemäß der aufrufenden Konvention für a zurückgegeben void*. Der aufrufende Code glaubt jedoch, dass die Funktion zurückkehrt int(da Sie sich dafür entschieden haben, nichts anderes zu sagen), und versucht daher, den Rückgabewert gemäß der aufrufenden Konvention für a zu lesen int. Zeigt pdaher nicht unbedingt auf den zugewiesenen Speicher. Es hat einfach so für IA32 funktioniert, weil an intund a void*die gleiche Größe haben und auf die gleiche Weise zurückgegeben wurden. Auf IA64 erhalten Sie den falschen Wert.
Steve Jessop
33

Höchstwahrscheinlich, weil Sie die Header-Datei für nicht einschließenmalloc und der Compiler Sie normalerweise davor warnt, bedeutet die Tatsache, dass Sie den Rückgabewert explizit umwandeln, dass Sie ihm mitteilen, dass Sie wissen, was Sie tun.

Das bedeutet, dass der Compiler erwartet int, dass ein zurückgegeben wird, von mallocdem er dann in einen Zeiger umgewandelt wird. Wenn sie unterschiedlich groß sind, wird dir das Kummer bereiten.

Aus diesem Grund haben Sie die Rückgabe niemalloc in C umgewandelt. Die void*Rückgabe wird implizit in einen Zeiger des richtigen Typs konvertiert (es sei denn, Sie haben den Header nicht eingefügt. In diesem Fall hätte er Sie wahrscheinlich vor dem möglicherweise unsicheren int gewarnt). in Zeigerumwandlung).

paxdiablo
quelle
Es tut mir leid, dass ich naiv klinge, aber ich habe immer angenommen, dass malloc einen leeren Zeiger zurückgibt, der in einen geeigneten Typ umgewandelt werden kann. Ich bin kein C-Programmierer und würde mich daher über etwas mehr Details freuen.
Benutzer7
5
@ user7: Ohne #include <stdlib.h> geht der C-Compiler davon aus, dass der Rückgabewert von malloc ein int ist.
Sashang
4
@ user7: Der void-Zeiger kann umgewandelt werden, wird jedoch in C nicht benötigt, da er void *implizit in einen anderen Zeigertyp konvertiert werden kann. int *p = malloc(sizeof(int))funktioniert, wenn der richtige Prototyp im Gültigkeitsbereich ist, und schlägt fehl, wenn dies nicht der Fall ist (da dann das Ergebnis angenommen wird int). Mit der Besetzung würden beide kompilieren und letztere würde zu Fehlern führen, wenn sizeof(int) != sizeof(void *).
2
@ user7 Aber wenn Sie nicht einschließen stdlib.h, weiß der Compiler nicht mallocund auch nicht seinen Rückgabetyp. Es wird also nur intals Standard angenommen.
Christian Rau
10

Aus diesem Grund kompilieren Sie niemals ohne Warnungen vor fehlenden Prototypen.

Aus diesem Grund haben Sie die Malloc-Rückkehr in C nie gewirkt.

Die Besetzung wird für die C ++ - Kompatibilität benötigt. Es gibt wenig Grund (lesen Sie: kein Grund hier), es wegzulassen.

C ++ - Kompatibilität ist nicht immer erforderlich und in einigen Fällen überhaupt nicht möglich, in den meisten Fällen jedoch sehr einfach.

Neugieriger
quelle
22
Warum um alles in der Welt würde es mich interessieren, wenn mein C-Code mit C ++ "kompatibel" ist? Es ist mir egal, ob es mit Perl oder Java oder Eiffel kompatibel ist oder ...
Stephen Canon
4
Wenn Sie garantieren, dass jemand auf der ganzen Linie Ihren C-Code nicht überprüft und loslegt, werde ich ihn mit einem C ++ - Compiler kompilieren, da dies funktionieren sollte!
Steven Lu
3
Das liegt daran, dass der meiste C-Code trivial C ++ -kompatibel gemacht werden kann.
Neugieriger