Pourquoi ce code segfault sur l'architecture 64 bits mais fonctionne bien sur 32 bits?

112

Je suis tombé sur le puzzle C suivant:

Q: Pourquoi le programme suivant fonctionne-t-il correctement sur IA-64, mais fonctionne-t-il correctement sur IA-32?

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

Je sais que la taille d' intune machine 64 bits peut ne pas être la même que la taille d'un pointeur ( intpeut être 32 bits et le pointeur 64 bits). Mais je ne sais pas comment cela se rapporte au programme ci-dessus. Des idées?

utilisateur7
la source
50
Est-ce quelque chose de stupide comme stdlib.hne pas être inclus?
user786653
3
Ce code fonctionne bien sur ma machine 64 bits. Il compile même sans avertissement si vous #include stdlib.h(pour malloc)
mpenkov
1
Oh! @ user786653 a cloué le bit important. Avec #include <stdlib.h>, c'est parfaitement trouvé, mais ce n'est pas dans la question.
8
@delnan - cela n'a pas à fonctionner comme ça cependant, il pourrait légitimement échouer sur une plate-forme où sizeof(int) == sizeof(int*), par exemple, des pointeurs étaient renvoyés via un registre différent de ints dans la convention d'appel utilisée.
Flexo
7
Dans un environnement C99, le compilateur doit vous donner au moins un avertissement concernant la déclaration implicite de malloc(). GCC dit: warning: incompatible implicit declaration of built-in function 'malloc'aussi.
Jonathan Leffler

Réponses:

130

Le cast int*masque le fait que sans le bon, #includele type de retour de mallocest supposé être int. IA-64 se trouve avoir sizeof(int) < sizeof(int*)ce qui rend ce problème évident.

(Notez également qu'en raison du comportement non défini, il peut toujours échouer même sur une plate-forme où la valeur sizeof(int)==sizeof(int*)est vraie, par exemple si la convention d'appel utilise des registres différents pour renvoyer des pointeurs que des entiers)

La FAQ comp.lang.c contient une entrée expliquant pourquoi la conversion du retour de mallocn'est jamais nécessaire et potentiellement mauvaise .

Flexo
la source
5
sans le #include approprié, pourquoi le type de retour de malloc est-il supposé être un int?
user7
11
@WTP - ce qui est une bonne raison de toujours utiliser newen C ++ et de toujours compiler C avec un compilateur C et non un compilateur C ++.
Flexo
6
@ user7 - ce sont les règles. Tout type de retour est supposé être ints'il n'est pas connu
Flexo
2
@vlad - la meilleure idée est de toujours déclarer des fonctions plutôt que de s'appuyer sur des déclarations implicites pour exactement cette raison. (Et ne pas lancer le retour de malloc)
Flexo
16
@ user7: "nous avons un pointeur p (de taille 64) qui pointe vers 32 bits de mémoire" - faux. L'adresse du bloc alloué par malloc a été renvoyée selon la convention d'appel pour a void*. Mais le code appelant pense que la fonction retourne int(puisque vous avez choisi de ne pas lui dire le contraire), il essaie donc de lire la valeur de retour selon la convention d'appel pour un int. Par conséquent, pne pointe pas nécessairement vers la mémoire allouée. Il se trouve que cela fonctionne pour IA32 car un intet un void*ont la même taille et sont retournés de la même manière. Sur IA64, vous obtenez la mauvaise valeur.
Steve Jessop
33

Très probablement parce que vous n'incluez pas le fichier d'en-tête pour mallocet, alors que le compilateur vous en avertit normalement, le fait que vous lancez explicitement la valeur de retour signifie que vous lui dites que vous savez ce que vous faites.

Cela signifie que le compilateur s'attend intà ce que un soit renvoyé à partir mallocduquel il est ensuite converti en pointeur. S'ils sont de tailles différentes, cela va vous causer du chagrin.

C'est pourquoi vous ne transtypez jamais le mallocretour en C.Le void*qu'il renvoie sera implicitement converti en un pointeur du type correct (sauf si vous n'avez pas inclus l'en-tête auquel cas il vous aurait probablement averti de l'int- conversion en pointeur).

paxdiablo
la source
désolé d'avoir l'air naïf, mais j'ai toujours supposé que malloc renvoie un pointeur vide qui peut être converti en un type approprié. Je ne suis pas un programmeur C et j'apprécierais donc un peu plus de détails.
user7
5
@ user7: sans le #include <stdlib.h> le compilateur C suppose que la valeur de retour de malloc est un int.
sashang du
4
@ user7: Le pointeur void peut être casté, mais il n'est pas nécessaire en C car il void *peut être converti en tout autre type de pointeur implicitement. int *p = malloc(sizeof(int))fonctionne si le prototype approprié est dans la portée et échoue si ce n'est pas le cas (car alors le résultat est supposé être int). Avec le casting, les deux compileraient et ce dernier entraînerait des erreurs quand sizeof(int) != sizeof(void *).
2
@ user7 Mais si vous n'incluez pas stdlib.h, le compilateur ne connaît pas mallocni son type de retour. Donc, il suppose simplement intpar défaut.
Christian Rau
10

C'est pourquoi vous ne compilez jamais sans avertissement sur les prototypes manquants.

C'est pourquoi vous ne lancez jamais le retour malloc en C.

Le cast est nécessaire pour la compatibilité C ++. Il y a peu de raisons (lire: aucune raison ici) de l'omettre.

La compatibilité C ++ n'est pas toujours nécessaire, et dans certains cas pas du tout possible, mais dans la plupart des cas, elle est très facile à réaliser.

curieux
la source
22
Pourquoi diable me soucierais-je si mon code C est "compatible" avec C ++? Je m'en fiche si c'est compatible avec perl ou java ou Eiffel ou ...
Stephen Canon
4
Si vous garantissez que quelqu'un sur toute la ligne ne va pas regarder votre code C et y aller, je vais le compiler avec un compilateur C ++ car cela devrait fonctionner!
Steven Lu
3
C'est le code le plus de cause C peuvent être trivialement fait C ++ compatible.
curiousguy du