Dlaczego ten kod nie działa w architekturze 64-bitowej, ale działa dobrze na wersji 32-bitowej?

112

Natknąłem się na następującą zagadkę C:

P: Dlaczego następujący program segfaulta na IA-64, ale działa dobrze na IA-32?

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

Wiem, że rozmiar intna komputerze 64-bitowym może nie być taki sam jak rozmiar wskaźnika ( intmoże wynosić 32 bity, a wskaźnik może mieć 64 bity). Ale nie jestem pewien, jak to się ma do powyższego programu. Jakieś pomysły?

użytkownik7
źródło
50
Czy to coś głupiego, jak stdlib.hnie uwzględnienie?
user786653
3
Ten kod działa dobrze na mojej 64-bitowej maszynie. Kompiluje się nawet bez ostrzeżeń, jeśli #include stdlib.h(dla malloc)
mpenkov
1
D'oh! @ user786653 przybił ważny fragment. Dzięki #include <stdlib.h>, jest doskonale znaleziony, ale nie o to chodzi.
8
@delnan - nie musi jednak tak działać, może legalnie zawieść na platformie, na której sizeof(int) == sizeof(int*), jeśli na przykład wskaźniki zostaną zwrócone, chociaż rejestr inny niż ints w używanej konwencji wywoływania.
Flexo
7
W środowisku C99 kompilator powinien dać przynajmniej ostrzeżenie o niejawnej deklaracji malloc(). GCC mówi: warning: incompatible implicit declaration of built-in function 'malloc'też.
Jonathan Leffler,

Odpowiedzi:

130

Rzut int*maskuje fakt, że bez właściwego #includeprzyjmuje się, że zwracany typ mallocto int. IA-64 tak się sizeof(int) < sizeof(int*)składa, że ​​ten problem jest oczywisty.

(Należy również zauważyć, że z powodu nieokreślonego zachowania może nadal zawieść nawet na platformie, na której sizeof(int)==sizeof(int*)jest prawdziwe, na przykład jeśli konwencja wywoływania używa innych rejestrów do zwracania wskaźników niż liczby całkowite)

W sekcji często zadawanych pytań dotyczących comp.lang.c znajduje się wpis omawiający, dlaczego odlewanie wyniku zwrotnego mallocnigdy nie jest potrzebne i jest potencjalnie złe .

Flexo
źródło
5
bez odpowiedniego #include, dlaczego przyjmuje się, że zwracanym typem malloc jest int?
user7,
11
@WTP - co jest dobrym powodem, aby zawsze używać neww C ++ i zawsze kompilować C za pomocą kompilatora C, a nie kompilatora C ++.
Flexo
6
@ user7 - takie są zasady. Zakłada się int, że jakikolwiek typ zwrotu jest nieznany
Flexo
2
@vlad - lepszym pomysłem jest zawsze deklarowanie funkcji, a nie poleganie na niejawnych deklaracjach właśnie z tego powodu. (I nie oddawać powrotu z malloc)
Flexo
16
@ user7: "mamy wskaźnik p (o rozmiarze 64) wskazujący na 32 bity pamięci" - źle. Adres bloku przydzielonego przez malloc został zwrócony zgodnie z konwencją wywoływania dla a void*. Ale kod wywołujący uważa, że ​​funkcja zwraca int(ponieważ zdecydowałeś nie mówić inaczej), więc próbuje odczytać wartość zwracaną zgodnie z konwencją wywoływania funkcji int. Stąd pnie nie muszą wskazywać na przydzielonej pamięci. Tak się złożyło, że zadziałało dla IA32, ponieważ inti a void*są tego samego rozmiaru i zwrócone w ten sam sposób. Na IA64 otrzymujesz złą wartość.
Steve Jessop,
33

Najprawdopodobniej dlatego, że nie dołączasz pliku nagłówkowego dla malloci, chociaż kompilator normalnie ostrzegłby Cię o tym, fakt, że jawnie rzutujesz wartość zwracaną, oznacza, że ​​mówisz mu, że wiesz, co robisz.

Oznacza to, że kompilator oczekuje intzwrócenia kodu, z mallocktórego następnie rzutuje na wskaźnik. Jeśli mają różne rozmiary, spowoduje to smutek.

Dlatego nigdy nie rzutujesz mallocpowrotu w C. void*Zwracany przez niego element zostanie niejawnie przekonwertowany na wskaźnik odpowiedniego typu (chyba że nie dołączyłeś nagłówka, w którym to przypadku prawdopodobnie ostrzegłby cię o potencjalnie niebezpiecznym int- do konwersji wskaźnika).

paxdiablo
źródło
przepraszam, że brzmię naiwnie, ale zawsze zakładałem, że malloc zwraca wskaźnik void, który można rzutować na odpowiedni typ. Nie jestem programistą C i dlatego doceniłbym trochę więcej szczegółów.
user7,
5
@ user7: bez #include <stdlib.h> kompilator C zakłada, że ​​zwracana wartość malloc to int.
sashang
4
@ user7: Wskaźnik void może być rzutowany, ale nie jest potrzebny w C, ponieważ void *można go niejawnie przekonwertować na dowolny inny typ wskaźnika. int *p = malloc(sizeof(int))działa, jeśli właściwy prototyp znajduje się w zakresie i kończy się niepowodzeniem, jeśli nie jest (ponieważ zakłada się, że wynik jest int). Z rzutowaniem oba skompilowałyby się, a to drugie spowodowałoby błędy, kiedy sizeof(int) != sizeof(void *).
2
@ user7 Ale jeśli tego nie zrobisz stdlib.h, kompilator nie będzie wiedział, mallocani jego typu zwracanego. Więc po prostu przyjmuje intjako domyślne.
Christian Rau,
10

Dlatego nigdy nie kompilujesz bez ostrzeżeń o brakujących prototypach.

Dlatego nigdy nie rzucasz powrotu Malloc w C.

Rzutowanie jest potrzebne do zapewnienia zgodności z C ++. Nie ma powodu (czytaj: nie ma powodu), aby go pominąć.

Zgodność z C ++ nie zawsze jest potrzebna, aw kilku przypadkach nie jest możliwa, ale w większości przypadków jest to bardzo łatwe do osiągnięcia.

ciekawy facet
źródło
22
Dlaczego, do licha, miałoby mnie obchodzić, czy mój kod w C jest „zgodny” z C ++? Nie obchodzi mnie, czy jest kompatybilny z perl, java, Eiffel, czy ...
Stephen Canon
4
Jeśli gwarantujesz, że ktoś na dole nie zajrzy do twojego kodu C i nie pójdzie, hej, skompiluję go za pomocą kompilatora C ++, ponieważ to powinno działać!
Steven Lu
3
To dlatego, że większość kodu C można w prosty sposób dostosować do C ++.
curiousguy