Bir C işaretçisini NULL olarak başlatmak mümkün müdür?


90

Şöyle şeyler yazıyordum

char *x=NULL;

varsayımıyla

 char *x=2;

charadres 2'ye bir işaretçi oluşturacaktır .

Ancak, GNU C Programlama Öğreticisi'ndeint *my_int_ptr = 2; , tamsayı değerini , tahsis edildiğinde 2rasgele adres ne olursa olsun sakladığını söylüyor my_int_ptr.

Bu benim kendi ima görünüyor char *x=NULLdeğeri ne olursa olsun atayan NULLbir etmek döküm charbellekte rastgele adrese etmektir.

Süre

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

int main()
{
    char *x=NULL;

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

    return EXIT_SUCCESS;
}

aslında yazdırıyor

BOŞ

onu derleyip çalıştırdığımda, tanımlanmamış davranışa veya en azından eksik belirtilmiş davranışa dayandığımdan ve yazmam gerektiğinden endişeleniyorum.

char *x;
x=NULL;

yerine.


72
Neyin int *x = whatever;yaptığı ile ne int *x; *x = whatever;yaptığı arasında çok kafa karıştırıcı bir fark var. int *x = whatever;aslında int *x; x = whatever;değil gibi davranıyor *x = whatever;.
user2357112 Monica'yı destekliyor

78
Bu öğretici, bu kafa karıştırıcı ayrımı yanlış yapmış gibi görünüyor.
user2357112 Monica'yı destekliyor

51
İnternette bir sürü boktan öğretici var! Derhal okumayı bırakın. Gerçekten berbat kitapları alenen utandırabileceğimiz bir ÇOK kara listeye ihtiyacımız var ...
Lundin

9
@MM 2017 yılını daha az berbat yapmaz. 80'lerden beri derleyicilerin ve bilgisayarların evrimi göz önüne alındığında, temelde bir doktormuşum ve 18. yüzyılda yazılmış tıp kitaplarını okuyormuşum gibi.
Lundin

13
Ben "olarak bu eğitici bunun yeterli olduğunu sanmıyorum ... GNU C Programlama Eğitimi"
marcelm

Yanıtlar:


114

Bir C işaretçisini NULL olarak başlatmak mümkün müdür?

TL; DR Evet, çok fazla.


Kılavuz üzerinde yapılan fiili iddiası gibi okur

Öte yandan, yalnızca ilk atamayı kullanırsanız, int *my_int_ptr = 2;program, işaret ettiği bellek konumunun içeriğini my_int_ptr2 değeri ile doldurmaya çalışacaktır. my_int_ptrÇöp ile dolu olduğu için herhangi bir adres olabilir. [...]

Eh, onlar vardır haklısınız, yanlış.

İfade için, ( şimdilik, tamsayıya dönüşüme göstericinin uygulama tanımlı bir davranış olduğu gerçeğini göz ardı ederek )

int * my_int_ptr = 2;

my_int_ptrBir değişken (tip işaretçi için int(: tamsayı ibrenin adres türü), Eğer bir değer depolamak), kendine ait bir adres vardır 2içine bu adresi.

Şimdi my_int_ptr, bir işaretçi türü olan, bunun, diyebiliriz işaret hafıza yerde "tip" değerine göre işaret düzenlenen değeri my_int_ptr. Yani, aslında değer atama ait işaretçi değişkeni, hafıza konumu değil değer gösterici ile gösterilen.

Yani, sonuç için

 char *x=NULL;

işaretçi değişkeni başlatır xiçin NULLdeğil, bellek adresinde değeri gösterici ile gösterilen .

Bu aynı şekilde

 char *x;
 x = NULL;    

Genişleme:

Şimdi, kesinlikle uyumlu olarak, şöyle bir ifade

 int * my_int_ptr = 2;

kısıtlama ihlali içerdiğinden yasa dışıdır. Açık olmak gerekirse,

  • my_int_ptr bir işaretçi değişkendir, tür int *
  • bir tamsayı sabiti, tanımı gereği 2türe sahiptir int.

o tarif / P1, §6.5.16.1 bölümde belirtilen, basit atama kurallarını ihlal ettiği için bu başlatma geçersiz yüzden onlar, "uyumlu" tip değildirler Lundin cevabı .

Başlatma işleminin basit atama kısıtlamalarıyla nasıl bağlantılı olduğu ile ilgilenen varsa, alıntı C11, bölüm §6.7.9, P11

Skaler için başlatıcı, isteğe bağlı olarak parantez içine alınmış tek bir ifade olacaktır. Nesnenin başlangıç ​​değeri, ifadenin değeridir (dönüştürmeden sonra); Basit atamayla aynı tür kısıtlamaları ve dönüştürmeleri geçerlidir, skalerin türünü, bildirilen türünün nitelenmemiş sürümü olarak alır.


Random832n @ Onlar şunlardır yanlış. Cevabımda ilgili kısmı aktardım, aksi takdirde lütfen düzeltin. Oh, ve kasıtlı olarak vurgu.
Sourav Ghosh

"... kısıtlama ihlali içerdiğinden yasa dışıdır. ... bir tamsayı değişmez, 2'nin tanımı gereği int türü vardır." sorunludur. Görünüşe göre çünkü 2bir int, ödev bir problem. Ama bundan daha fazlası. NULLayrıca bir int, bir olabilir int 0. Bu sadece char *x = 0;iyi tanımlanmıştır ve char *x = 2;değildir. 6.3.2.3 işaretçiler 3 (btw: C tanımlamaz tamsayı hazır bilgi sadece dize ve bileşik Birebir . 0Bir olan tam sayı sabit )
chux - Eski Monica

@chux Çok haklısın, ama char *x = (void *)0;uygun olmak değil mi? yoksa değeri veren sadece diğer ifadelerde 0mi?
Sourav Ghosh

10
@SouravGhosh: değere sahip tamsayı sabitleri 0özeldir: genel tamsayı ifadelerini işaretçi türlerine açıkça atama kurallarından ayrı olarak örtük olarak boş göstericilere dönüştürürler.
Steve Jessop

1
1974 C Referans Kılavuzunda açıklanan dil, bildirimlerin başlatma ifadelerini belirtmesine izin vermedi ve bu tür ifadelerin olmaması, "bildirim aynalarının kullanımını" çok daha pratik hale getiriyor. Sözdizimi int *p = somePtrExpressionIMHO, değerini ayarlıyor gibi göründüğü için oldukça korkunç, *pama aslında değerini belirliyor p.
supercat

53

Öğretici yanlış. ISO C'de int *my_int_ptr = 2;bir hatadır. GNU C'de, ile aynı anlama gelir int *my_int_ptr = (int *)2;. Bu, tamsayıyı 2bir şekilde derleyici tarafından belirlendiği gibi bir bellek adresine dönüştürür .

Bu adres tarafından adreslenen konumda (varsa) herhangi bir şey kaydetmeye çalışmaz. Yazmaya devam ederseniz *my_int_ptr = 5;, numarayı 5o adresin adreslediği konuma kaydetmeye çalışırdı .


1
Tam sayıdan işaretçi dönüşümüne uygulama tanımlı olduğunu bilmiyordum. Bilgi için teşekkürler.
taskinoor

1
@taskinoor Bu cevapta olduğu gibi, yalnızca bir oyuncu kadrosuyla zorlamanız durumunda bir dönüşüm olduğunu lütfen unutmayın. Oyuncular için değilse, kod derlenmemelidir.
Lundin

2
@taskinoor: Evet, C'deki çeşitli dönüşümler oldukça kafa karıştırıcı. Bu Q, dönüşümler hakkında ilginç bilgilere sahiptir: C: İşaretçi türleri arasında çevrim ne zaman tanımsız bir davranış değildir? .
sleske

17

Öğreticinin neden yanlış olduğunu açıklığa kavuşturmak için int *my_int_ptr = 2;, bir "kısıtlama ihlali" dir, derlemesine izin verilmeyen bir koddur ve derleyici, onunla karşılaştığınızda size bir teşhis vermelidir.

6.5.16.1 Basit atama uyarınca:

Kısıtlamalar

Aşağıdakilerden biri geçerli olacaktır:

  • sol işlenen atomik, nitelenmiş veya niteliksiz aritmetik tipe ve sağda aritmetik tipe sahiptir;
  • sol işlenen, sağın türüyle uyumlu bir yapı veya birleşim türünün atomik, nitelikli veya niteliksiz bir versiyonuna sahiptir;
  • sol işlenen atomik, nitelikli veya nitelenmemiş işaretçi tipine sahiptir ve (sol işlenenin ldeğer dönüşümünden sonra sahip olacağı tür dikkate alındığında) her iki işlenen de uyumlu türlerin nitelikli veya nitelenmemiş sürümlerine işaretçilerdir ve sol tarafından gösterilen tür, sağ tarafından gösterilen tür niteleyicileri;
  • sol işlenen atomik, nitelikli veya nitelenmemiş işaretçi tipine sahiptir ve (sol işlenenin ldeğer dönüşümünden sonra sahip olacağı tür dikkate alındığında) bir işlenen bir nesne türüne bir göstericidir ve diğeri de nitelenmemiş veya nitelenmemiş bir void ve sol tarafından işaret edilen tür, sağ tarafından gösterilen türün tüm niteleyicilerini içerir;
  • sol işlenen atomik, nitelikli veya niteliksiz bir göstericidir ve sağ ise boş bir gösterici sabitidir; veya
  • sol işlenen atomik, nitelikli veya niteliksiz _Bool tipine sahiptir ve sağ ise bir göstericidir.

Bu durumda, sol işlenen, niteliksiz bir göstericidir. Sağ işlenenin bir tamsayı (aritmetik tip) olmasına izin verildiğinden hiçbir yerde bahsetmez. Yani kod C standardını ihlal ediyor.

GCC'nin standart bir C derleyicisi olduğunu açıkça söylemediğiniz sürece kötü davrandığı bilinmektedir. Kodu olduğu gibi -std=c11 -pedantic-errorsderlerseniz, olması gerektiği gibi doğru bir şekilde teşhis verecektir.


4
-pedantik-hataları önerdiği için oy verildi. Muhtemelen ilgili -Wpedantic'i kullanacağım.
fagricipni

2
İfadenizin bir istisnası, doğru işlenenin bir tamsayı olmasına izin verilmez: Bölüm 6.3.2.3, "0 değerine sahip bir tamsayı sabit ifadesi veya tipe dönüştürülen böyle bir ifade void *, boş gösterici sabiti olarak adlandırılır " der . Alıntıdaki ikinci-son madde noktasına dikkat edin. Bu nedenle int* p = 0;yasal bir yazma şeklidir int* p = NULL;. İkincisi daha net ve daha geleneksel olmasına rağmen.
Davislor

1
Bu da patolojik şaşırtmayı int m = 1, n = 2 * 2, * p = 1 - 1, q = 2 - 1;yasal kılıyor .
Davislor

@Davislor, bu yanıttaki standart alıntıda 5. madde ile kapsanan (daha sonra özetin muhtemelen bundan bahsetmesi gerektiğini kabul edin)
MM

1
@chux İyi biçimlendirilmiş bir programın, intptr_taçıkça sağ tarafta izin verilen türlerden birine dönüştürmesi gerektiğine inanıyorum . Yani, void* a = (void*)(intptr_t)b;4. maddeye göre yasaldır, ancak (intptr_t)bne uyumlu bir işaretçi tipi, ne a void*, ne de boş işaretçi sabiti ve void* ane aritmetik tip ne de _Bool. Standart, dönüşümün yasal olduğunu söylüyor, ancak örtük olmadığını söylüyor.
Davislor

15

int *my_int_ptr = 2

tamsayı değeri 2'yi, tahsis edildiğinde my_int_ptr'deki rastgele adrese depolar.

Bu tamamen yanlış. Bu gerçekten yazılmışsa, lütfen daha iyi bir kitap veya eğitim alın.

int *my_int_ptr = 2adres 2'yi gösteren bir tamsayı işaretçisi tanımlar. Adrese erişmeye çalışırsanız büyük olasılıkla çökme yaşarsınız 2.

*my_int_ptr = 2yani intsatırda olmadan, rastgele adresin my_int_ptrişaret ettiği iki değeri saklar . Bunu söyledikten NULLsonra, tanımlandığında bir işaretçiye atayabilirsiniz . char *x=NULL;tamamen geçerlidir C.

Düzenleme: Bunu yazarken, tamsayıdan işaretçiye dönüşümün uygulama tanımlı davranış olduğunu bilmiyordum. Ayrıntılar için lütfen @MM ve @SouravGhosh tarafından verilen iyi yanıtlara bakın.


1
Tamamen yanlıştır çünkü başka herhangi bir nedenle değil bir kısıtlama ihlalidir. Özellikle, bu yanlıştır: "int * my_int_ptr = 2, adres 2'yi gösteren bir tamsayı işaretçisi tanımlar".
Lundin

@Lundin: "Başka bir sebepten ötürü değil" ifadenizin kendisi yanlış ve yanıltıcıdır. Tür uyumluluk sorununu giderirseniz, öğreticinin yazarının işaretçi başlatmalarının ve atamalarının nasıl çalıştığını büyük ölçüde yanlış beyan ettiği gerçeğiyle baş başa kalırsınız.
Orbit'te Hafiflik Yarışları

14

C işaretçileriyle ilgili pek çok kafa karışıklığı, başlangıçta kodlama stiliyle ilgili yapılan çok kötü bir seçimden kaynaklanıyor ve dilin sözdizimindeki çok kötü küçük bir seçimle destekleniyor.

int *x = NULL;doğru C, ama çok yanıltıcı, hatta saçma olduğunu söyleyebilirim ve birçok acemi için dilin anlaşılmasını engelledi. Daha sonra yapabileceğimizi düşündürür ki *x = NULL;bu elbette imkansızdır. Sen değişkenin türü değil, bakın intve değişkenin ismi değil *x, ne yaptığı *beyanında ile işbirliği içinde herhangi bir fonksiyonel bir rol oynamaktadır =. Tamamen açıklayıcıdır. Yani, daha mantıklı olan şey şudur:

int* x = NULL;bu da doğru C'dir, ancak orijinal K&R kodlama stiline uymasa da. Türün olduğunu int*ve işaretçi değişkeninin olduğunu mükemmel bir şekilde xaçıkça ortaya koyar, böylece değerin NULLiçine kaydedildiği x, yani bir gösterici olan, başlatılmamış kişiler için bile açıkça belli olur int.

Dahası, bir kural türetmeyi kolaylaştırır: yıldız, değişken adından uzaktaysa, o zaman bu bir bildirimdir, oysa isme eklenen yıldız işaretçi referansıdır.

Yani, şimdi daha da biz birini yapabilirsiniz aşağı bunu çok daha anlaşılır hale gelir x = NULL;ya *x = 2;da daha kolay bir acemi nasıl görmesini sağlar başka bir deyişle variable = expressionyol açar pointer-type variable = pointer-expressionve dereferenced-pointer-variable = expression. (Başlatılanlar için 'ifade' ile 'değer' demek istiyorum.)

Dilin sözdizimindeki talihsiz seçim, yerel değişkenleri int i, *p;bildirirken, hangisinin bir tamsayı ve bir tamsayı için bir işaretçi bildirdiğini söyleyebilmenizdir , bu nedenle bu, kişiyi *ismin yararlı bir parçası olduğuna inanmaya yönlendirir . Ama öyle değil ve bu sözdizimi sadece ilginç bir özel durumdur, kolaylık sağlamak için eklenmiştir ve kanımca, yukarıda önerdiğim kuralı geçersiz kıldığı için asla var olmamalıydı. Bildiğim kadarıyla, dilin başka hiçbir yerinde bu sözdizimi anlamlı değildir, ancak olsa bile, işaretçi türlerinin C'de tanımlanma biçiminde bir tutarsızlığa işaret eder. Her yerde, tek değişkenli bildirimlerde, parametre listelerinde, struct üyeleri, vb. içinde işaretçilerinizi type* pointer-variableyerine ilan edebilirsiniz type *pointer-variable; tamamen yasal ve daha mantıklı.


int *x = NULL; is correct C, but it is very misleading, I would even say nonsensical,... katılmadığımı kabul etmeliyim. It makes one think.... düşünmeyi bırak, önce bir C kitabı oku, alınma.
Sourav Ghosh

^^ bu bana çok mantıklı geliyordu. Öyleyse, sanırım özneldir.
Mike Nakis

5
@SouravGhosh Bence C iki işaretçi bildirecek şekilde tasarlanmış olmalıydıint* somePtr, someotherPtr , aslında ben yazıyordum int* somePtrama bu sizin tarif ettiğiniz hataya neden oluyor.
fagricipni

1
@fagricipni Bundan dolayı çoklu değişken bildirim sözdizimini kullanmayı bıraktım. Değişkenlerimi tek tek bildiriyorum. Gerçekten aynı satırda olmasını istiyorsam, virgül yerine noktalı virgülle ayırırım. "Bir yer kötüyse, o yere gitme."
Mike Nakis

2
@fagricipni Eğer linux'u sıfırdan tasarlayabilseydim, createonun yerine kullanırdım creat. :) Mesele şu ki, nasıl olduğu ve buna uyum sağlamak için kendimizi şekillendirmemiz gerekiyor. Günün sonunda her şey kişisel seçiminize dönüyor, kabul edin.
Sourav Ghosh

6

Birçok mükemmel cevaba ortogonal bir şey eklemek istiyorum. Aslında, ile başlatmak NULLkötü bir uygulama olmaktan uzaktır ve eğer bu işaretçi dinamik olarak ayrılmış bir bellek bloğunu depolamak için kullanılıp kullanılmayabilirse kullanışlı olabilir.

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

Göre yana , ISO-IEC 9899 standardı free bağımsız değişken olduğunda nop olan NULL(aynı çizgide daha anlamlı ya da bir şey) Yukarıdaki kod okunaklı.


5
C kodunun C ++ olarak derlenmesi gerekmedikçe, malloc sonucunu C'ye dönüştürmek gereksizdir.
kedi

Haklısın, void*gerektiği gibi dönüştürülür. Ancak bir C ve C ++ derleyicisiyle çalışan koda sahip olmanın faydaları olabilir.
Luca Citi

1
@LucaCiti C ve C ++ farklı dillerdir. Biri için yazılmış bir kaynak dosyayı, diğeri için tasarlanmış bir derleyici kullanarak derlemeye çalışırsanız sizi bekleyen sadece hatalar vardır. Pascal araçlarını kullanarak derleyebileceğiniz C kodu yazmaya çalışmak gibi.
Evil Köpek Pie

1
İyi tavsiye. İşaretçi sabitlerimi her zaman bir şeye ilklendirmeye çalışırım. Modern C'de, bu genellikle onların nihai değeri olabilir ve medias res'teconst bildirilen işaretçiler olabilir , ancak bir işaretçinin (bir döngüde veya tarafından kullanılan gibi) değiştirilebilir olması gerektiğinde bile , onu daha önce kullanıldığı yerde hataları yakalamaya ayarlayarak gerçek değeriyle belirlenir. Çoğu sistemde, yeniden referanslama , başarısızlık noktasında bir segment arızasına neden olur (istisnalar olsa da), oysa başlatılmamış bir işaretçi çöp içerir ve ona yazmak keyfi belleği bozar. realloc()NULLNULL
Davislor

1
Ayrıca, bir işaretçinin içerdiği hata ayıklayıcıda görmek çok kolaydır NULL, ancak bir çöp işaretçisini geçerli olandan ayırmak çok zor olabilir. Bu nedenle, tüm işaretçilerin her zaman geçerli olmasını veya NULLbildirim anından itibaren olmasını sağlamak faydalıdır .
Davislor


1

Doğru.

int main()
{
    char * x = NULL;

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

    return EXIT_SUCCESS;
}

Bu işlev, yaptığı şey için doğrudur. Karakter işaretçisine x 0 adresini atar. Yani, x işaretçisini 0 bellek adresine işaret eder.

Alternatif:

int main()
{
    char* x = 0;

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

    return EXIT_SUCCESS;
}

Ne istediğine dair tahminim şu:

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.

"Karakter işaretçisine x 0 adresini atar." -> Belki. C , işaretçinin değerini belirtmez , yalnızca bu char* x = 0; if (x == 0)doğru olacaktır. İşaretçilerin tam sayı olması gerekmez.
chux - Monica'yı eski durumuna getir

'X işaretçisini 0 hafıza adresine yönlendirmez'. İşaretçi değerini , 0 veya NULL ile karşılaştırılarak test edilebilecek belirtilmemiş geçersiz bir değere ayarlar . Gerçek işlem uygulama tanımlıdır. Burada asıl soruyu cevaplayan hiçbir şey yok.
user207421
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.