Apakah mungkin untuk menginisialisasi pointer C ke NULL?

90

Saya telah menulis hal-hal seperti

char *x=NULL;

dengan asumsi itu

 char *x=2;

akan membuat charpointer ke alamat 2.

Tapi, dalam Tutorial Pemrograman GNU C dikatakan bahwa int *my_int_ptr = 2;menyimpan nilai integer 2ke alamat acak apa pun my_int_ptrsaat dialokasikan.

Ini sepertinya menyiratkan bahwa saya sendiri char *x=NULLmenetapkan apa pun nilai NULLcast ke a charke beberapa alamat acak dalam memori.

Sementara

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *x=NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

tidak, pada kenyataannya, mencetak

adalah NULL

ketika saya mengompilasi dan menjalankannya, saya khawatir bahwa saya mengandalkan perilaku yang tidak ditentukan, atau setidaknya perilaku yang kurang ditentukan, dan bahwa saya harus menulis

char *x;
x=NULL;

sebagai gantinya.

fagricipni.dll
sumber
72
Ada perbedaan yang sangat membingungkan antara apa yang int *x = whatever;dilakukan dan apa yang int *x; *x = whatever;dilakukannya. int *x = whatever;sebenarnya berperilaku seperti int *x; x = whatever;, tidak *x = whatever;.
user2357112 mendukung Monica
78
Tutorial ini tampaknya salah dalam membedakan perbedaan yang membingungkan.
user2357112 mendukung Monica
51
Begitu banyak tutorial menyebalkan di web! Berhenti membaca segera. Kami benar-benar membutuhkan SO daftar hitam di mana kami dapat mempermalukan buku-buku jelek secara publik ...
Lundin
9
@MM Yang tidak membuatnya kurang jelek di tahun 2017. Mengingat evolusi kompiler dan komputer sejak tahun 80-an, pada dasarnya sama saja seperti saya adalah seorang dokter dan membaca buku kedokteran yang ditulis selama abad ke-18.
Lundin
13
Saya tidak berpikir memenuhi syarat tutorial ini sebagai " The GNU C Tutorial Pemrograman" ...
marcelm

Jawaban:

114

Apakah mungkin untuk menginisialisasi pointer C ke NULL?

TL; DR Ya, sangat banyak.


The klaim yang sebenarnya dibuat pada panduan dibaca seperti

Di sisi lain, jika Anda hanya menggunakan satu tugas awal, int *my_int_ptr = 2;program akan mencoba untuk mengisi isi dari lokasi memori yang ditunjuk oleh my_int_ptrnilai 2. Karena my_int_ptrdiisi dengan sampah, itu bisa alamat apapun. [...]

Yah, mereka adalah salah, Anda benar.

Untuk pernyataan, ( mengabaikan, untuk saat ini, fakta bahwa penunjuk ke konversi integer adalah perilaku yang ditentukan implementasi )

int * my_int_ptr = 2;

my_int_ptradalah variabel (tipe pointer ke int), ia memiliki alamat sendiri (tipe: alamat pointer ke integer), Anda menyimpan nilai 2ke dalam yang alamat.

Sekarang, my_int_ptrsebagai tipe penunjuk, bisa kita katakan, ini menunjuk ke nilai "type" di lokasi memori yang ditunjukkan oleh nilai yang dipegang my_int_ptr. Jadi, Anda pada dasarnya menempatkan nilai dari variabel pointer, bukan nilai dari lokasi memori yang ditunjuk oleh pointer.

Jadi, sebagai kesimpulan

 char *x=NULL;

menginisialisasi variabel pointer xke NULL, bukan nilai di alamat memori yang ditunjukkan oleh pointer .

Ini sama dengan

 char *x;
 x = NULL;    

Ekspansi:

Sekarang, menjadi benar-benar sesuai, pernyataan seperti

 int * my_int_ptr = 2;

ilegal, karena melibatkan pelanggaran batasan. Untuk lebih jelasnya,

  • my_int_ptr adalah variabel penunjuk, ketik int *
  • konstanta integer, 2memiliki tipe int, menurut definisi.

dan mereka bukan tipe yang "kompatibel", jadi inisialisasi ini tidak valid karena melanggar aturan penetapan sederhana, yang disebutkan dalam bab §6.5.16.1 / P1, yang dijelaskan dalam jawaban Lundin .

Jika ada yang tertarik bagaimana inisialisasi dihubungkan dengan batasan tugas sederhana, kutipan C11, bab §6.7.9, P11

Penginisialisasi untuk skalar harus berupa ekspresi tunggal, secara opsional diapit oleh tanda kurung. Nilai awal objek adalah ekspresi (setelah konversi); batasan jenis dan konversi yang sama seperti untuk penetapan sederhana berlaku, menjadikan jenis skalar sebagai versi yang tidak memenuhi syarat dari jenis yang dideklarasikan.

Sourav Ghosh
sumber
@ Random832n Mereka adalah salah. Saya telah mengutip bagian terkait dalam jawaban saya, mohon koreksi saya jika sebaliknya. Oh, dan penekanannya disengaja.
Sourav Ghosh
"... ilegal, karena melibatkan pelanggaran batasan. ... literal integer, 2 memiliki tipe int, menurut definisi." bermasalah. Kedengarannya seperti karena 2adalah int, penugasan adalah masalah. Tapi lebih dari itu. NULLmungkin juga merupakan int, sebuah int 0. Hanya saja yang char *x = 0;didefinisikan dengan baik dan char *x = 2;tidak. 6.3.2.3 Pointer 3 (BTW: C tidak mendefinisikan literal bilangan bulat , hanya literal string dan literal gabungan . 0Merupakan konstanta bilangan bulat )
chux - Pasang kembali Monica
@chux Anda sangat benar, tetapi bukankah begitu char *x = (void *)0;, untuk menyesuaikan diri? atau hanya dengan ekspresi lain yang menghasilkan nilai 0?
Sourav Ghosh
10
@SouravGhosh: konstanta integer dengan nilai 0bersifat spesial: konstanta tersebut secara implisit mengonversi ke pointer nol secara terpisah dari aturan biasa untuk secara eksplisit mentransmisikan ekspresi integer umum ke jenis pointer.
Steve Jessop
1
Bahasa yang dijelaskan oleh Manual Referensi C 1974 tidak mengizinkan deklarasi untuk menentukan ekspresi inisialisasi, dan kurangnya ekspresi tersebut membuat "penggunaan cermin deklarasi" jauh lebih praktis. Sintaksnya int *p = somePtrExpressionIMHO agak mengerikan karena sepertinya itu mengatur nilai *ptetapi sebenarnya mengatur nilai p.
supercat
53

Tutorialnya salah. Dalam ISO C, int *my_int_ptr = 2;adalah kesalahan. Di GNU C, artinya sama dengan int *my_int_ptr = (int *)2;. Ini mengubah integer 2menjadi alamat memori, dengan cara tertentu seperti yang ditentukan oleh kompilator.

Itu tidak mencoba untuk menyimpan apa pun di lokasi yang dialamatkan oleh alamat itu (jika ada). Jika Anda melanjutkan menulis *my_int_ptr = 5;, maka itu akan mencoba menyimpan nomor 5di lokasi yang dialamatkan oleh alamat itu.

MM
sumber
1
Saya tidak tahu bahwa konversi integer ke pointer adalah implementasi yang ditentukan. Terima kasih untuk informasi.
taskinoor
1
@taskinoor Harap dicatat bahwa ada konversi hanya jika Anda memaksanya oleh pemeran, seperti dalam jawaban ini. Jika bukan karena pemeran, kode tidak boleh dikompilasi.
Lundin
2
@taskinoor: Ya, berbagai konversi di C cukup membingungkan. Q ini memiliki informasi menarik tentang konversi: C: Kapan casting antara tipe pointer bukan perilaku tidak terdefinisi? .
sleske
17

Untuk memperjelas mengapa tutorial salah, int *my_int_ptr = 2;merupakan "pelanggaran batasan", ini adalah kode yang tidak diperbolehkan untuk dikompilasi dan kompilator harus memberi Anda diagnosis saat menghadapinya.

Sesuai 6.5.16.1 Penugasan sederhana:

Kendala

Salah satu dari berikut ini akan berlaku:

  • operan kiri memiliki tipe aritmatika atom, memenuhi syarat, atau tidak memenuhi syarat, dan operan kanan memiliki tipe aritmatika;
  • operan kiri memiliki versi atom, memenuhi syarat, atau tidak memenuhi syarat dari struktur atau tipe gabungan yang kompatibel dengan tipe kanan;
  • operan kiri memiliki tipe pointer atomik, memenuhi syarat, atau tidak memenuhi syarat, dan (mengingat tipe yang akan dimiliki operan kiri setelah konversi nilai l) kedua operan adalah pointer ke versi yang memenuhi syarat atau tidak memenuhi syarat dari tipe yang kompatibel, dan tipe yang ditunjukkan oleh kiri memiliki semuanya kualifikasi dari jenis yang ditunjuk oleh hak;
  • operan kiri memiliki tipe penunjuk atomik, memenuhi syarat, atau tidak memenuhi syarat, dan (mengingat jenis yang akan dimiliki operan kiri setelah konversi nilai l) satu operan adalah penunjuk ke tipe objek, dan yang lainnya adalah penunjuk ke versi yang memenuhi syarat atau tidak memenuhi syarat kosong, dan tipe yang ditunjukkan di sebelah kiri memiliki semua kualifikasi dari tipe yang ditunjukkan oleh kanan;
  • operan kiri adalah pointer atomik, memenuhi syarat, atau tidak memenuhi syarat, dan kanan adalah konstanta pointer nol; atau
  • operan kiri memiliki tipe atomic, qualified, or unqualified _Bool, dan right adalah pointer.

Dalam hal ini operan kiri adalah penunjuk yang tidak memenuhi syarat. Tidak ada yang menyebutkan bahwa operan kanan diperbolehkan menjadi integer (tipe aritmatika). Jadi kodenya melanggar standar C.

GCC diketahui berperilaku buruk kecuali Anda secara eksplisit memberitahukannya sebagai compiler C standar. Jika Anda mengkompilasi kode sebagai -std=c11 -pedantic-errors, itu akan memberikan diagnosis yang benar seperti yang harus dilakukan.

Lundin
sumber
4
suara positif untuk menyarankan -pedantic-error. Meskipun saya mungkin akan menggunakan -Wpedantic terkait.
fagricipni
2
Satu pengecualian untuk pernyataan Anda bahwa operan kanan tidak boleh berupa bilangan bulat: Bagian 6.3.2.3 mengatakan, "Ekspresi konstanta bilangan bulat dengan nilai 0, atau ekspresi seperti itu yang diketik void *, disebut konstanta penunjuk nol." Perhatikan poin poin kedua hingga terakhir dalam kutipan Anda. Oleh karena itu, int* p = 0;cara menulis adalah legal int* p = NULL;. Meskipun yang terakhir lebih jelas dan lebih konvensional.
Davislor
1
Yang membuat kebingungan patologis int m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;juga legal.
Davislor
@Davislor yang dicakup oleh poin-poin 5 dalam kutipan standar dalam jawaban ini (setuju bahwa ringkasan setelah itu mungkin harus menyebutkannya)
MM
1
@chux Saya yakin program yang dibentuk dengan baik perlu mengonversi secara intptr_teksplisit ke salah satu jenis yang diizinkan di sisi kanan. Artinya, void* a = (void*)(intptr_t)b;sah menurut poin 4, tetapi (intptr_t)bbukan merupakan jenis penunjuk yang kompatibel, atau a void*, atau konstanta penunjuk nol, dan void* abukan juga jenis aritmatika _Bool. Standar mengatakan bahwa konversi itu legal, tetapi tidak implisit.
Davislor
15

int *my_int_ptr = 2

menyimpan nilai integer 2 ke alamat acak apa pun yang ada di my_int_ptr saat dialokasikan.

Ini sepenuhnya salah. Jika ini benar-benar ditulis, dapatkan buku atau tutorial yang lebih baik.

int *my_int_ptr = 2mendefinisikan pointer integer yang menunjuk ke alamat 2. Kemungkinan besar Anda akan mengalami crash jika Anda mencoba mengakses alamat 2.

*my_int_ptr = 2, yaitu tanpa intdi baris, menyimpan nilai dua ke alamat acak mana pun my_int_ptryang ditunjuk. Setelah mengatakan ini, Anda dapat menetapkan NULLke pointer saat sudah ditentukan. char *x=NULL;sangat valid C.

Sunting: Saat menulis ini, saya tidak tahu bahwa konversi integer ke pointer adalah implementasi perilaku yang ditentukan. Silakan lihat jawaban bagus oleh @MM dan @SouravGhosh untuk detailnya.

taskinoor.dll
sumber
1
Ini sepenuhnya salah karena itu adalah pelanggaran batasan, bukan karena alasan lain. Secara khusus, ini salah: "int * my_int_ptr = 2 mendefinisikan pointer integer yang menunjuk ke alamat 2".
Lundin
@ Lundin: Frasa Anda "bukan karena alasan lain" itu sendiri salah dan menyesatkan. Jika Anda memperbaiki masalah kompatibilitas tipe, Anda masih tertinggal dengan fakta bahwa penulis tutorial sangat keliru dalam menggambarkan bagaimana inisialisasi pointer dan tugas bekerja.
Balapan Ringan di Orbit
14

Banyak kebingungan tentang petunjuk C berasal dari pilihan yang sangat buruk yang awalnya dibuat terkait gaya pengkodean, diperkuat oleh pilihan kecil yang sangat buruk dalam sintaks bahasa.

int *x = NULL;benar C, tetapi sangat menyesatkan, saya bahkan akan mengatakan tidak masuk akal, dan itu telah menghambat pemahaman bahasa bagi banyak pemula. Itu membuat orang berpikir bahwa nanti kita bisa melakukan *x = NULL;yang tentu saja tidak mungkin. Anda lihat, jenis variabelnya bukan int, dan nama variabelnya bukan *x, dan *deklarasi in juga tidak memainkan peran fungsional apa pun yang berkolaborasi dengan =. Ini murni deklaratif. Jadi, yang lebih masuk akal adalah ini:

int* x = NULL;yang juga benar C, meskipun tidak sesuai dengan gaya pengkodean K&R asli. Itu membuatnya sangat jelas bahwa tipenya adalah int*, dan variabel pointernya adalah x, sehingga menjadi bukti jelas bahkan bagi yang belum tahu bahwa nilainya NULLdisimpan ke xdalamnya, yang merupakan pointer ke int.

Selain itu, akan lebih mudah untuk mendapatkan aturan: ketika bintang jauh dari nama variabel maka itu adalah deklarasi, sedangkan bintang yang dilampirkan ke nama adalah dereferensi penunjuk.

Jadi, sekarang menjadi jauh lebih dapat dimengerti bahwa lebih jauh kita bisa melakukan x = NULL;atau *x = 2;dengan kata lain memudahkan seorang pemula untuk melihat bagaimana variable = expressionmengarah ke pointer-type variable = pointer-expressiondan dereferenced-pointer-variable = expression. (Untuk awal, dengan 'ekspresi' maksud saya 'nilai r'.)

Pilihan yang tidak menguntungkan dalam sintaks bahasa adalah ketika mendeklarasikan variabel lokal Anda dapat mengatakan int i, *p;yang mendeklarasikan integer dan pointer ke integer, sehingga membuat orang percaya bahwa *adalah bagian yang berguna dari nama tersebut. Tetapi tidak, dan sintaks ini hanyalah kasus khusus yang unik, ditambahkan untuk kenyamanan, dan menurut saya seharusnya tidak pernah ada, karena itu membatalkan aturan yang saya usulkan di atas. Sejauh yang saya tahu, tidak ada tempat lain dalam bahasa ini yang memiliki makna sintaksis ini, tetapi meskipun demikian, ini menunjukkan perbedaan dalam cara jenis penunjuk didefinisikan di C. Di mana pun, dalam deklarasi variabel tunggal, dalam daftar parameter, di anggota struct, dll. Anda dapat mendeklarasikan pointer Anda sebagai type* pointer-variablepengganti type *pointer-variable; itu sangat legal dan lebih masuk akal.

Mike Nakis
sumber
int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,... Saya harus setuju untuk tidak setuju. It makes one think.... berhenti berpikir, baca buku C dulu, jangan tersinggung.
Sourav Ghosh
^^ ini akan sangat masuk akal bagi saya. Jadi, saya kira itu subjektif.
Mike Nakis
5
@SouravGhosh Menurut pendapat saya, saya pikir C seharusnya dirancang sedemikian rupa sehingga int* somePtr, someotherPtrmenyatakan dua petunjuk, pada kenyataannya, saya biasa menulis int* somePtrtetapi itu mengarah ke bug yang Anda gambarkan.
fagricipni
1
@fagricipni Saya berhenti menggunakan sintaks deklarasi variabel ganda karena ini. Saya mendeklarasikan variabel saya satu per satu. Jika saya benar-benar menginginkannya pada baris yang sama, saya pisahkan dengan titik koma, bukan koma. "Jika suatu tempat buruk, jangan pergi ke tempat itu."
Mike Nakis
2
@fagricipni Nah, jika saya bisa mendesain linux dari awal, saya akan menggunakan createsebagai gantinya creat. :) Intinya adalah, begitulah adanya dan kita perlu membentuk diri kita sendiri untuk beradaptasi dengan itu. Itu semua bermuara pada pilihan pribadi pada akhirnya, setuju.
Sourav Ghosh
6

Saya ingin menambahkan sesuatu yang ortogonal ke banyak jawaban yang sangat bagus. Sebenarnya, menginisialisasi ke NULLjauh dari praktik yang buruk dan mungkin berguna jika penunjuk itu mungkin atau mungkin tidak digunakan untuk menyimpan blok memori yang dialokasikan secara dinamis.

int * p = NULL;
...
if (...) {
    p = (int*) malloc(...);
    ...
}
...
free(p);

Karena menurut standar ISO-IEC 9899 free adalah nop jika argumennya adalah NULL, kode di atas (atau sesuatu yang lebih bermakna di sepanjang baris yang sama) adalah sah.

Luca Citi
sumber
5
Tidak ada gunanya mentransmisikan hasil malloc di C, kecuali kode C itu juga harus dikompilasi sebagai C ++.
kucing
Anda benar, yang void*bertobat sesuai kebutuhan. Tetapi memiliki kode yang berfungsi dengan kompiler C dan C ++ dapat memiliki keuntungan.
Luca Citi
1
@LucaCiti C dan C ++ adalah bahasa yang berbeda. Hanya ada kesalahan yang menunggu Anda jika Anda mencoba mengompilasi file sumber yang ditulis untuk satu file menggunakan kompilator yang dirancang untuk yang lain. Ini seperti mencoba menulis kode C yang dapat Anda kompilasi menggunakan alat Pascal.
Evil Dog Pie
1
Saran yang bagus. Saya (mencoba) selalu menginisialisasi konstanta penunjuk saya ke sesuatu. Di C modern, ini biasanya bisa menjadi nilai akhir mereka dan bisa menjadi constpointer yang dideklarasikan di res media , tetapi bahkan ketika pointer harus bisa berubah (seperti yang digunakan dalam loop atau oleh realloc()), mengaturnya untuk NULLmenangkap bug di tempat yang digunakan sebelumnya itu diatur dengan nilai aslinya. Pada kebanyakan sistem, dereferensi NULLmenyebabkan segfault pada titik kegagalan (meskipun ada pengecualian), sedangkan penunjuk yang tidak diinisialisasi berisi sampah dan penulisan ke sana merusak memori arbitrer.
Davislor
1
Selain itu, sangat mudah untuk melihat di debugger yang berisi penunjuk NULL, tetapi bisa sangat sulit untuk membedakan penunjuk sampah dari penunjuk yang valid. Jadi sangat membantu untuk memastikan bahwa semua petunjuk selalu valid atau NULL, sejak saat deklarasi.
Davislor
1

ini adalah penunjuk nol

int * nullPtr = (void*) 0;
Ahmed Nabil El-Gawahergy
sumber
1
Ini menjawab judul, tetapi bukan tubuh pertanyaannya.
Fabio mengatakan Reinstate Monica
1

Ini benar.

int main()
{
    char * x = NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

Fungsi ini benar untuk apa yang dilakukannya. Ini memberikan alamat 0 ke char pointer x. Artinya, ini mengarahkan penunjuk x ke alamat memori 0.

Alternatif:

int main()
{
    char* x = 0;

    if ( !x )
        printf(" x points to NULL\n");

    return EXIT_SUCCESS;
}

Tebakan saya tentang apa yang Anda inginkan adalah:

int main()
{
    char* x = NULL;
    x = alloc( sizeof( char ));
    *x = '2';

    if ( *x == '2' )
        printf(" x points to an address/location that contains a '2' \n");

    return EXIT_SUCCESS;
}

x is the street address of a house. *x examines the contents of that house.
Vanderdecken
sumber
"Ini memberikan alamat 0 ke char pointer x." -> Mungkin. C tidak menentukan nilai pointer, hanya itu yang char* x = 0; if (x == 0)benar. Pointer tidak harus berupa bilangan bulat.
chux - Kembalikan Monica
Itu tidak 'mengarahkan pointer x ke alamat memori 0'. Ini menetapkan nilai penunjuk ke nilai tidak valid yang tidak ditentukan yang dapat diuji dengan membandingkannya dengan 0, atau NULL. Operasi sebenarnya ditentukan oleh implementasi. Tidak ada apa pun di sini yang menjawab pertanyaan yang sebenarnya.
Marquis dari Lorne