Perché questo codice segfault su architettura a 64 bit ma funziona bene su 32 bit?


112

Mi sono imbattuto nel seguente puzzle C:

D: Perché il seguente programma segfault su IA-64, ma funziona bene su IA-32?

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

So che la dimensione di intsu una macchina a 64 bit potrebbe non essere la stessa della dimensione di un puntatore ( intpotrebbe essere di 32 bit e il puntatore potrebbe essere di 64 bit). Ma non sono sicuro di come questo si riferisca al programma di cui sopra. Qualche idea?


50
È qualcosa di sciocco come stdlib.hnon essere inclusi?
user786653

3
Questo codice funziona bene sulla mia macchina a 64 bit. Si compila anche senza avvisi se #include stdlib.h(per malloc)
mpenkov

1
D'oh! @ user786653 ha inchiodato la parte importante. Con #include <stdlib.h>, è perfettamente trovato, ma non è questo il problema.

8
@delnan - non deve funzionare così, tuttavia, potrebbe legittimamente fallire su una piattaforma in cui sizeof(int) == sizeof(int*), se ad esempio i puntatori venissero restituiti tramite un registro diverso per ints nella convenzione di chiamata utilizzata.
Flexo

7
In un ambiente C99, il compilatore dovrebbe darti almeno un avvertimento sulla dichiarazione implicita di malloc(). GCC dice: warning: incompatible implicit declaration of built-in function 'malloc'anche.
Jonathan Leffler

Risposte:


130

Il cast per int*maschera il fatto che senza l'appropriato si presume che il #includetipo di ritorno mallocsia int. Capita che IA-64 abbia il sizeof(int) < sizeof(int*)che rende ovvio questo problema.

(Nota anche che a causa del comportamento indefinito potrebbe comunque fallire anche su una piattaforma dove sizeof(int)==sizeof(int*)è vero, ad esempio se la convenzione di chiamata usasse registri diversi per restituire puntatori rispetto agli interi)

La FAQ di comp.lang.c contiene una voce che spiega perché il casting del ritorno da mallocnon è mai necessario e potenzialmente dannoso .


5
senza il corretto #include, perché si presume che il tipo restituito di malloc sia un int?
user7

11
@WTP - che è una buona ragione per usare sempre newin C ++ e compilare sempre C con un compilatore C e non un compilatore C ++.
Flexo

6
@ user7 - queste sono le regole. Si presume che qualsiasi tipo di restituzione sia intse non noto
Flexo

2
@vlad: l'idea migliore è dichiarare sempre le funzioni piuttosto che fare affidamento su dichiarazioni implicite proprio per questo motivo. (E non lanciare il ritorno da malloc)
Flexo

16
@ user7: "abbiamo un puntatore p (di dimensione 64) che punta a 32 bit di memoria" - sbagliato. L'indirizzo del blocco allocato da malloc è stato restituito secondo la convenzione di chiamata per un file void*. Ma il codice chiamante pensa che la funzione ritorni int(dato che hai scelto di non dirgli diversamente), quindi cerca di leggere il valore restituito in base alla convenzione di chiamata per un file int. Quindi pnon non necessariamente puntare alla memoria allocata. È successo così che funzionasse per IA32 perché an inte a void*hanno le stesse dimensioni e restituiti allo stesso modo. Su IA64 ottieni il valore sbagliato.
Steve Jessop,

33

Molto probabilmente perché non stai includendo il file di intestazione per malloce, mentre il compilatore normalmente ti avviserebbe di questo, il fatto che stai esplicitamente eseguendo il cast del valore restituito significa che gli stai dicendo che sai cosa stai facendo.

Ciò significa che il compilatore si aspetta intche venga restituito un messaggio dal mallocquale esegue il cast a un puntatore. Se sono di dimensioni diverse, ti causerà dolore.

Questo è il motivo per cui non esegui mai il cast del mallocritorno in C. Il valore void*restituito verrà convertito implicitamente in un puntatore del tipo corretto (a meno che non hai incluso l'intestazione, nel qual caso probabilmente ti avrebbe avvertito dell'intestazione potenzialmente non sicura conversione in puntatore).


scusate per sembrare ingenuo, ma ho sempre pensato che malloc restituisse un puntatore void che può essere lanciato su un tipo appropriato. Non sono un programmatore C e quindi apprezzerei un po 'più di dettaglio.
user7

5
@ user7: senza #include <stdlib.h> il compilatore C presume che il valore di ritorno di malloc sia un int.
sashang

4
@ user7: il puntatore void può essere lanciato, ma non è necessario in C poiché void *può essere convertito implicitamente in qualsiasi altro tipo di puntatore. int *p = malloc(sizeof(int))funziona se il prototipo corretto è nell'ambito e fallisce se non lo è (perché si presume che il risultato sia int). Con il cast, entrambi verrebbero compilati e quest'ultimo comporterebbe errori quando sizeof(int) != sizeof(void *).

2
@ user7 Ma se non includi stdlib.h, il compilatore non conosce malloce nemmeno il suo tipo di ritorno. Quindi assume solo intcome impostazione predefinita.
Christian Rau,

10

Questo è il motivo per cui non compili mai senza avvertimenti sui prototipi mancanti.

Questo è il motivo per cui non hai mai lanciato il ritorno di malloc in C.

Il cast è necessario per la compatibilità C ++. Non c'è motivo (leggi: nessun motivo qui) per ometterlo.

La compatibilità C ++ non è sempre necessaria e in alcuni casi non è affatto possibile, ma nella maggior parte dei casi è molto facile da ottenere.


22
Perché diavolo dovrei preoccuparmi se il mio codice C è "compatibile" con C ++? Non mi interessa se è compatibile con perl o java o Eiffel o ...
Stephen Canon

4
Se garantisci che qualcuno in fondo alla linea non guarderà il tuo codice C e se ne andrà, hey lo compilerò con un compilatore C ++ perché dovrebbe funzionare!
Steven Lu

3
Questo perché la maggior parte del codice C può essere banalmente reso compatibile con C ++.
curioso
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.