Diziler, işaretçiler ve işlevler için C sözdizimi neden bu şekilde tasarlandı?


16

Benzer soruları gördükten ve sorduktan sonra

int (*f)(int (*a)[5])C anlamı ne ?

ve insanların C sözdizimini anlamalarına yardımcı olacak bir program yaptıklarını görerek bile , yardım edemem ama merak ediyorum:

C sözdizimi neden bu şekilde tasarlandı?

Örneğin, işaretçiler tasarlıyor olsaydım, "işaretçiyi 10 öğeli işaretçi dizisine" çevirirdim

int*[10]* p;

ve değil

int* (*p)[10];

çoğu insanın aynı fikirde olduğunu düşünürüm.

Merak ediyorum, neden sezgisel olmayan sözdizimi? Sözdiziminin çözdüğü (belki de belirsizliği olan) belirli bir sorun var mıydı?


2
Bunun ve bunun gibi soruların gerçek bir cevabı olmadığını biliyorsunuz. Sağ? Elde edeceğiniz şey sadece tahminlerdir.
BЈовић

7
@VJo - "gerçek" (yani nesnel) bir cevap olabilir - dil yazarları ve standart komiteleri bu kararların çoğunu açıkça haklı çıkarmıştır (veya en azından açıklamıştır).
resly

Önerilen sözdiziminizin C sözdiziminden daha fazla veya daha az "sezgisel" olduğunu düşünmüyorum. C budur; öğrendikten sonra, bu soruları bir daha asla hatırlamayacaksınız. Eğer öğrenmediyseniz ... iyi, belki de asıl sorun budur.
Caleb

1
@Caleb: Bu kadar kolay bir şekilde nasıl sonuca varacağınız komik, çünkü öğrendim ve hala bu soruyu sordum ...
user541686

1
cdeclKomut karmaşık C bildirimleri kodunu çözmek için çok kullanışlıdır. Ayrıca cdecl.org'da bir web arayüzü var .
Keith Thompson

Yanıtlar:


16

Tarihini anlamam iki ana noktaya dayanıyor olması ...

İlk olarak, dil yazarları sözdizimini tip merkezli değil, değişken merkezli yapmayı tercih ettiler. Bu da onların beyanı bakmak için bir programcı istedi ve düşünmek ise, "Ben deyim yazarsanız *func(arg), bu bir neden olacak int; ben yazarsam *arg[N]ben bir float olacak" "yerine funcbir işlev alarak bir gösterici olmalıdır bu ve dönen o ".

Vikipedi'de C girişi iddia:

Ritchie'nin fikri, tanımlayıcıları kullanımlarına benzeyen bağlamlarda beyan etmekti: "beyanname kullanımı yansıtır".

... ne yazık ki, sizin için genişletilmiş teklifi bulmak için elimde olmak zorunda değil.

İkincisi, keyfi dolaylı düzeylerle uğraşırken tutarlı bir beyan sözdizimi bulmak gerçekten, gerçekten zordur. Örneğiniz, orada yarasa düşündüğünüz türü ifade etmek için iyi çalışabilir, ancak bu tür bir diziye bir işaretçi alan ve başka bazı iğrenç karmaşa döndüren bir işleve ölçeklenir mi? (Belki öyle, ama kontrol ettin mi? Kanıtlayabilir misin? ).

Unutmayın, C'nin başarısı, derleyicilerin birçok farklı platform için yazılmasından kaynaklanmaktadır ve bu nedenle derleyicilerin yazılmasını kolaylaştırmak için bir dereceye kadar okunabilirliği göz ardı etmek daha iyi olabilirdi.

Bunu söyledikten sonra ben dil grameri veya derleyici yazımı konusunda uzman değilim. Ama bilinecek çok şey olduğunu biliyorum;)


2
"derleyicilerin yazılmasını kolaylaştırır" ... ancak C'nin ayrıştırılması zor olduğu için (yalnızca C ++ ile doldurulur) bilinir.
Jan Hudec

1
@JanHudec - Şey ... evet. Bu su geçirmez bir ifade değil. Ancak C, bağlamsız bir dilbilgisi olarak ayrıştırmak imkansız olsa da, bir kişi ayrıştırmanın bir yolunu bulduktan sonra, bu zor bir adım olmaktan çıkar. Ve gerçek öyle, oldu nedeniyle K & R bazı denge kurduğu gerekir, böylece kolayca derleyiciler dışarı patlama edememek insanlara İlk günlerinde üretken. (Richard Gabriel'in rezil " The Worse is Better" in yükselişinde, yeni bir platform için C derleyicisi yazmanın kolay olduğu gerçeğini kabul etti - ve
bemoans için aldı

Bu arada düzeltildiğim için mutluyum - Ayrıştırma ve dilbilgisi hakkında çok fazla şey bilmiyorum. Daha çok tarihsel gerçeklerden çıkarımda bulunuyorum.
detly

12

C dilinin birçok tuhaflığı, bilgisayarların tasarlanırken çalışma şekli ile açıklanabilir. Çok sınırlı miktarda depolama belleği vardı, bu nedenle kaynak kodu dosyalarının boyutunu en aza indirmek çok önemliydi . 70'lerde ve 80'lerde programlama pratiği, kaynak kodun mümkün olduğunca az karakter içerdiğinden ve tercihen aşırı kaynak kodu yorumları olmadığından emin olmaktı.

Bu, elbette bugün sabit disklerde neredeyse sınırsız depolama alanı ile saçma. Ancak C'nin bu kadar tuhaf bir sözdizimine sahip olmasının nedeninin bir parçası.


Özellikle dizi işaretçileriyle ilgili olarak, ikinci örneğiniz int (*p)[10];(evet sözdizimi çok kafa karıştırıcı) olmalıdır. Belki de "on dizi dizisine int işaretçi" olarak okurdum ... bu biraz mantıklı. Parantez için değilse, derleyici bunu on işaretli bir dizi olarak yorumlayacak ve bu da bildirime tamamen farklı bir anlam verecektir.

Dizi işaretçileri ve işlev işaretleyicilerinin her ikisi de C'de oldukça belirsiz bir sözdizimine sahip olduğundan, yapılacak mantıklı şey tuhaflığı tanımlamaktır. Belki de şöyle:

Belirsiz örnek:

int func (int (*arr_ptr)[10])
{
  return 0;
}

int main()
{
  int array[10];
  int (*arr_ptr)[10]  = &array;
  int (*func_ptr)(int(*)[10]) = &func;

  func_ptr(arr_ptr);
}

Belirsiz, eşdeğer örnek:

typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);


int func (array_t* arr_ptr)
{
  return 0;
}

int main()
{
  int        array[10];
  array_t*   arr_ptr  = &array; /* non-obscure array pointer */
  funcptr_t  func_ptr = &func;  /* non-obscure function pointer */

  func_ptr(arr_ptr);
}

İşlev işaretçileri dizileriyle uğraşıyorsanız, işler daha da belirsizleşebilir. Ya da hepsinden en belirsiz olanı: işlev işaretçileri döndüren işlevler (hafif kullanışlı). Bu tür şeyler için typedefs kullanmazsanız, çabucak delirirsiniz.


Ah, sonunda makul bir cevap. :-) Belirli bir sözdiziminin aslında kaynak kod boyutunu nasıl küçültebileceğini merak ediyorum, ama her nasılsa bu makul bir fikir ve mantıklı. Teşekkürler. +1
user541686

Kaynak kod boyutu hakkında daha az ve derleyici yazma hakkında daha fazla olduğunu söyleyebilirim, ama kesinlikle "tuhaflık uzakta typdef" için +1. Bunu yapabileceğimi fark ettiğim gün zihinsel sağlığım önemli ölçüde iyileşti.
resly

2
Kaynak kodu boyutu konusunda [Alıntı gerekli]. Hiç böyle bir sınırlama duymadım (belki de "herkesin bildiği" bir şey).
Sean McMillan

1
IBM, DEC ve XEROX kitinde COBOL, Assembler, CORAL ve PL / 1'deki 70'lerde programları kodladım ve daha önce hiç kaynak kod boyutu sınırlamasıyla karşılaşmadım. Dizi boyutu, yürütülebilir boyut, program adı boyutu ile ilgili sınırlamalar - ancak asla kaynak kodu boyutu.
James Anderson

1
@Sean McMillan: Kaynak kodu boyutunun bir sınırlama olduğunu düşünmüyorum (o zaman Pascal gibi ayrıntılı dillerin oldukça popüler olduğunu düşünün). Ve bu durumda olsa bile, kaynak kodunu önceden ayrıştırmanın ve uzun anahtar kelimeleri kısa bir baytlık kodlarla (örneğin, kullanılan bazı Temel tercümanlar gibi) değiştirmenin çok kolay olacağını düşünüyorum. Bu yüzden "C daha az belleğe sahip olduğu bir dönemde icat edildiğinden" argümanı biraz zayıf buluyorum.
Giorgio

7

Oldukça basit: int *paraçlar *pbir int; int a[5]bu a[i]bir int anlamına gelir .

int (*f)(int (*a)[5])

Bu *fbir işlev, *abeş tamsayıdan oluşan bir dizidir, yani beş tamsayıdan oluşan bir diziye fbir işaretçi alan ve int döndüren bir işlevdir. Bununla birlikte, C'de bir diziye bir işaretçi geçirmek yararlı değildir.

C bildirimleri çok nadiren karmaşık hale gelir.

Ayrıca, typedefs kullanarak açıklığa kavuşabilirsiniz:

typedef int vec5[5];
int (*f)(vec5 *a);

4
Bu kaba geliyorsa özür dilerim (öyle demek istemiyorum), ama sorunun tüm noktasını kaçırdığınızı düşünüyorum ...: \
user541686

2
@Mehrdad: Kernighan ve Ritchie'nin zihninde ne olduğunu söyleyemem; Sana sözdiziminin arkasındaki mantığı söyledim. Çoğu insan hakkında bir şey bilmiyorum ama önerilen sözdiziminin daha net olduğunu düşünmüyorum.
kevin cline

Katılıyorum - böyle karmaşık bir bildiri görmek olağandışı.
Caleb

C tasarım önceleyen Beyannameleri typedef, const, volatile, bildirimleri içinde şeyler başlatmak için ve yeteneği. Beyan sözdiziminin can sıkıcı belirsizliklerinin birçoğu (örneğin , türe veya beyannameye int const *p, *q;bağlanması gerekip gerekmediği const), dilde başlangıçta tasarlandığı gibi ortaya çıkamamıştır. Dilin tür ve bildiri arasına iki nokta üst üste eklemesini, ancak niteleyiciler olmadan yerleşik "ayrılmış sözcük" türlerini kullanırken atlanmasına izin vermesini diliyorum. Anlamı int: const *p,*q;ve int const *: p,*q;net olurdu.
supercat

3

* [] Değişkenine bağlı işleçler olarak düşünmeniz gerektiğini düşünüyorum. * bir değişkenden önce, [] sonra yazılır.

Şimdi tür ifadesini okuyalım

int* (*p)[10];

En içteki eleman p'dir, bu nedenle bir değişkendir,

p

şu anlama gelir: p bir değişkendir.

Değişken * olmadan önce, * operatörü her zaman atıfta bulunduğu ifadeden önce konur, bu nedenle,

(*p)

şu anlama gelir: p değişkeni bir işaretçidir. () Olmadan sağdaki [] operatörü daha yüksek önceliğe sahip olurdu, yani

**p[]

olarak ayrıştırılır

*(*(p[]))

Bir sonraki adım []: başka () olmadığı için, [] dıştan * daha yüksek önceliğe sahiptir, bu nedenle

(*p)[]

şu anlama gelir: (p değişkeni bir göstericidir) bir diziye. Sonra ikincimiz var *:

* (*p)[]

şu anlama gelir: ((değişken p bir göstericidir) bir diziye) işaretçiler

Son olarak, en düşük önceliğe sahip olan int işlecine (tür adı) sahipsiniz:

int* (*p)[]

şu anlama gelir: (((p değişkeni bir dizi) işaretçi)) tamsayı.

Bu nedenle, tüm sistem operatörlerle tip ifadelerine dayanmaktadır ve her operatörün kendi öncelik kuralları vardır. Bu çok karmaşık tiplerin tanımlanmasına izin verir.


0

Düşünmeye başladığınızda çok zor değil ve C hiç bu kadar kolay bir dil olmadı. Ve int*[10]* pgerçekten bundan daha kolay değil int* (*p)[10] Ve ne tür bir k olurdu?int*[10]* p, k;


2
k başarısız bir kod inceleme olurdu, derleyici ne yapacağını çalışabilir, ben bile rahatsız olabilir, ama programcı ne istediğini
çalışamıyorum

ve k neden kod incelemesinde başarısız oldu?
Dainius

1
çünkü kod okunamıyor ve sürdürülemez. Kod düzeltmek için doğru değil, açıkça doğru ve bakım sırasında doğru kalması muhtemel. K türünün ne olacağını sormak zorunda olmanız, kodun bu temel gereksinimleri yerine getiremediğinin bir işaretidir.
mattnz

1
Esasen, aynı satırda farklı tiplerde 3 (bu örnekte) 3 değişken bildirimi vardır, örneğin int * p, int i [10] ve int k. Bu kabul edilemez. Değişkenlerin int genişlik, yükseklik, derinlik gibi bir ilişki biçimine sahip olması koşuluyla, aynı türde birden fazla bildirim kabul edilebilir. Birçok kişinin int * p kullanarak programladığını unutmayın, bu yüzden 'int * p, i;'
mattnz

1
@Mattnz'ın söylemeye çalıştığı şey, istediğiniz kadar akıllı olabilmenizdir, ancak niyetiniz açık olmadığında ve / veya kodunuz kötü yazılmış / okunamaz olduğunda anlamsızdır. Bu tür şeyler genellikle bozuk kod ve zaman kaybına neden olur. Artı pointer to intve intaynı türden değiller, bu yüzden ayrı ayrı bildirilmelidirler. Dönemi. Adamı dinle. Bir sebepten dolayı 18 bin temsilcisi var.
Braden Best
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.