Mengapa kode ini segfault pada arsitektur 64-bit tetapi berfungsi dengan baik pada 32-bit?

112

Saya menemukan teka-teki C berikut:

T: Mengapa program berikut dipisahkan pada IA-64, tetapi berfungsi dengan baik pada IA-32?

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

Saya tahu bahwa ukuran intpada mesin 64 bit mungkin tidak sama dengan ukuran pointer ( intbisa 32 bit dan pointer bisa 64 bit). Tetapi saya tidak yakin bagaimana ini berhubungan dengan program di atas. Ada ide?

pengguna7
sumber
50
Apakah itu sesuatu yang konyol seperti stdlib.htidak disertakan?
user786653
3
Kode ini berjalan dengan baik di mesin 64 bit saya. Bahkan mengkompilasi tanpa peringatan jika Anda #include stdlib.h(untuk malloc)
mpenkov
1
D'oh! @ user786653 menyelesaikan bagian penting. Dengan #include <stdlib.h>, itu ditemukan dengan sempurna, tetapi itu bukan pertanyaannya.
8
@delnan - itu tidak harus bekerja seperti itu, itu bisa secara sah gagal pada platform di mana sizeof(int) == sizeof(int*), jika misalnya pointer dikembalikan melalui register yang berbeda intdalam konvensi pemanggilan yang digunakan.
Flexo
7
Di lingkungan C99, kompilator harus memberi Anda setidaknya peringatan tentang deklarasi implisit dari malloc(). GCC mengatakan: warning: incompatible implicit declaration of built-in function 'malloc'juga.
Jonathan Leffler

Jawaban:

130

Para pemeran untuk int*menutupi fakta bahwa tanpa #includetipe pengembalian yang tepat mallocdiasumsikan int. IA-64 kebetulan memiliki sizeof(int) < sizeof(int*)yang membuat masalah ini menjadi jelas.

(Perhatikan juga bahwa karena perilaku tidak terdefinisi, ia masih bisa gagal bahkan pada platform yang sizeof(int)==sizeof(int*)memegang true, misalnya jika konvensi pemanggil menggunakan register yang berbeda untuk mengembalikan pointer daripada bilangan bulat)

The comp.lang.c FAQ memiliki entri membahas mengapa casting kembali dari malloctidak pernah diperlukan dan berpotensi buruk .

Flexo
sumber
5
tanpa #include yang tepat, mengapa tipe kembalian malloc diasumsikan sebagai int?
pengguna7
11
@WTP - yang merupakan alasan bagus untuk selalu menggunakan newdalam C ++ dan selalu mengompilasi C dengan kompiler C dan bukan kompilator C ++.
Flexo
6
@ user7 - itulah aturannya. Setiap jenis pengembalian diasumsikan intjika tidak diketahui
Flexo
2
@vlad - ide yang lebih baik adalah selalu mendeklarasikan fungsi daripada mengandalkan deklarasi implisit karena alasan ini. (Dan tidak memberikan pengembalian dari malloc)
Flexo
16
@ user7: "kami memiliki penunjuk p (berukuran 64) yang menunjuk ke memori 32 bit" - salah. Alamat blok yang dialokasikan oleh malloc dikembalikan sesuai dengan konvensi pemanggilan untuk a void*. Tetapi kode pemanggil menganggap fungsi tersebut kembali int(karena Anda memilih untuk tidak memberi tahu sebaliknya), jadi ia mencoba membaca nilai yang dikembalikan sesuai dengan konvensi pemanggilan untuk int. Oleh karena itu ptidak tidak selalu menunjuk ke memori yang dialokasikan. Kebetulan bekerja untuk IA32 karena an intdan a void*berukuran sama, dan dikembalikan dengan cara yang sama. Pada IA64 Anda mendapatkan nilai yang salah.
Steve Jessop
33

Kemungkinan besar karena Anda tidak menyertakan file header untuk mallocdan, sementara kompiler biasanya memperingatkan Anda tentang hal ini, fakta bahwa Anda secara eksplisit mentransmisikan nilai yang dikembalikan berarti Anda memberi tahu bahwa Anda tahu apa yang Anda lakukan.

Itu berarti kompilator mengharapkan sebuah intdikembalikan dari mallocmana ia kemudian ditransmisikan ke sebuah pointer. Jika ukurannya berbeda, itu akan membuat Anda sedih.

Inilah sebabnya mengapa Anda tidak pernah mentransmisikan mallocpengembalian dalam C. Yang void*dikembalikan akan secara implisit dikonversi ke penunjuk dari jenis yang benar (kecuali Anda belum menyertakan header dalam hal ini mungkin akan memperingatkan Anda tentang int- yang berpotensi tidak aman. konversi ke penunjuk).

paxdiablo
sumber
maaf karena terdengar naif, tetapi saya selalu berasumsi bahwa malloc mengembalikan pointer kosong yang dapat dilemparkan ke tipe yang sesuai. Saya bukan seorang programmer C dan karenanya akan menghargai sedikit lebih detail.
pengguna7
5
@ user7: tanpa #include <stdlib.h> kompilator C mengasumsikan nilai kembalian malloc adalah int.
sashang
4
@ user7: Void pointer dapat digunakan, tetapi tidak diperlukan di C karena void *dapat dikonversi ke tipe pointer lainnya secara implisit. int *p = malloc(sizeof(int))berfungsi jika prototipe yang tepat ada dalam cakupan dan gagal jika tidak (karena hasilnya diasumsikan seperti itu int). Dengan cast, keduanya akan dikompilasi dan yang terakhir akan menghasilkan error saat sizeof(int) != sizeof(void *).
2
@ user7 Tetapi jika Anda tidak menyertakannya stdlib.h, kompilator tidak tahu mallocdan juga tipe kembaliannya. Jadi itu hanya mengasumsikan intsebagai default.
Christian Rau
10

Inilah sebabnya mengapa Anda tidak pernah mengompilasi tanpa peringatan tentang prototipe yang hilang.

Inilah mengapa Anda tidak pernah melemparkan pengembalian malloc di C.

Pemeran diperlukan untuk kompatibilitas C ++. Ada sedikit alasan (baca: tidak ada alasan di sini) untuk menghilangkannya.

Kompatibilitas C ++ tidak selalu diperlukan, dan dalam beberapa kasus tidak memungkinkan sama sekali, tetapi dalam banyak kasus hal ini sangat mudah dicapai.

penasaran
sumber
22
Mengapa saya harus peduli jika kode C saya "kompatibel" dengan C ++? Saya tidak peduli apakah itu kompatibel dengan perl atau java atau Eiffel atau ...
Stephen Canon
4
Jika Anda menjamin seseorang di telepon tidak akan melihat kode C Anda dan pergi, hei saya akan mengkompilasinya dengan kompiler C ++ karena itu seharusnya berfungsi!
Steven Lu
3
Yang ini penyebab paling kode C dapat trivial dibuat C ++ kompatibel.
penasaran