it-swarm-tr.com

C dizisinde başlatma ile ilgili karışıklık

C dilinde, eğer böyle bir diziyi ilklendirirseniz:

int a[5] = {1,2};

daha sonra dizinin açıkça başlatılmayan tüm elemanları, sıfır ile örtük olarak başlatılacaktır.

Ancak, şöyle bir dizi başlatırsam:

int a[5]={a[2]=1};

printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);

çıktı:

1 0 1 0 0

Anlamıyorum, neden a[0], 1 yerine 0 yazdırıyor? Tanımlanmamış davranış mı?

Not: Bu soru bir röportajda soruldu.

98
M.S Chaudhari

TL; DR: int a[5]={a[2]=1}; davranışının en azından C99'da iyi tanımlandığını sanmıyorum.

İşin komik yanı, bana mantıklı gelen tek şey, sorduğunuz kısım: a[0], 1 olarak ayarlandı çünkü atama operatörü atanan değeri döndürür. Belirsiz olan her şey.

Eğer kod int a[5] = { [2] = 1 } olsaydı, her şey kolay olurdu: Belirlenmiş bir başlatıcı ayarı a[2] - 1 ve diğer her şey 0. Fakat { a[2] = 1 } ile bir atama ifadesi içeren atanmamış bir başlatıcıya sahibiz ve bir tavşan deliğinden aşağıya düşeriz.


İşte şimdiye kadar ne buldum:

  • a yerel bir değişken olmalıdır.

    6.7.8 Başlatma

    1. Statik depolama süresi olan bir cisim için bir başlatıcıdaki tüm ifadeler sabit ifadeler veya string değişmezleri olacaktır.

    a[2] = 1 sabit bir ifade değil, bu yüzden a otomatik saklamaya sahip olmalı.

  • a kendi başlangıç ​​durumuna göre kapsamda.

    6.2.1 Tanımlayıcıların kapsamları

    1. Structure, union ve numaralandırma etiketlerinin, etiketi bildiren bir tür belirticideki etiketin görünmesinden hemen sonra başlayan kapsamı vardır. Her numaralandırma sabiti, bir numaralandırıcı listesinde tanımlayıcı numaralandırıcının görünmesinden hemen sonra başlayan bir kapsamı vardır. Başka bir tanımlayıcının, bildiriminin tamamlanmasından hemen sonra başlayan kapsamı vardır.

    Bildirgesi a[5], bu nedenle değişkenler kendi başlangıçlarında kapsam içindedir.

  • a kendi ilklendirmesinde yaşıyor.

    6.2.4 Nesnelerin saklama süresi

    1. Tanımlayıcısı hiçbir bağlantı olmadan ve depolama sınıfı belirticisi static olmadan bildirilen bir nesnede otomatik depolama süresi vardır .

    2. Değişken uzunluklu dizi türüne sahip olmayan bir nesne için, kullanım ömrü, o bloğun çalışmasına bitene kadar ilişkili olduğu bloğa girişten itibaren uzanır. (Kapalı bir bloğa girme veya bir işlev çağırma, geçerli bloğun yürütülmesini askıya alır, ancak sona ermez.) Blok tekrar tekrar girilirse, her seferinde nesnenin yeni bir örneği oluşturulur. Nesnenin başlangıç ​​değeri belirsizdir. Nesne için bir başlatma belirtilirse, blok yürütme işleminde bildirime her ulaştığında gerçekleştirilir; Aksi halde, bildirgeye her ulaştığında değer belirsizleşir.

  • a[2]=1'den sonra bir sıra noktası var.

    6.8 Tablolar ve bloklar

    1. A tam ifade , başka bir ifadenin veya bildirgenin parçası olmayan bir ifadedir. Aşağıdakilerin her biri tam bir ifadedir: bir başlatıcı; ifade ifadesindeki ifade; bir seçim ifadesinin kontrol ifadesi (if veya switch); while veya do ifadesinin kontrol ifadesi; for ifadesinin (isteğe bağlı) ifadelerinin her biri; return ifadesinde (isteğe bağlı) ifadesi. Tam ifadenin sonu bir sıralama noktasıdır.

    Ör. int foo[] = { 1, 2, 3 } içinde { 1, 2, 3 } bölümü, her birinden sonra bir sıra noktası olan ayraç içine alınmış bir başlatıcı listesidir.

  • Başlatma, başlatıcı liste sırasına göre gerçekleştirilir.

    6.7.8 Başlatma

    1. Her ayraç içine alınmış başlatıcı listesi, ilişkili geçerli bir nesneye sahiptir. Atama olmadığında, geçerli nesnenin alt nesneleri, geçerli nesnenin türüne göre sırayla başlatılır: artan alt simge sırasındaki dizi elemanları, bildirim sırasındaki yapı üyeleri ve bir birliğin ilk üyesi. [...]

    1. Başlatma, başlatıcı liste sırasına göre gerçekleştirilecektir, her başlatıcı, aynı alt nesne için önceden listelenen başlatıcıları geçersiz kılan belirli bir alt nesne için sağlanmıştır; açıkça başlatılmayan tüm alt nesneler, statik depolama süresine sahip nesnelerle aynı şekilde başlatılmalıdır.
  • Ancak, başlatıcı ifadelerin sırayla değerlendirilmesi gerekmez.

    6.7.8 Başlatma

    1. Başlatma listesi ifadeleri arasında herhangi bir yan etkinin meydana gelme sırası belirtilmemiş.

Ancak, bu hala bazı soruları cevapsız bırakıyor:

  • Sıralama noktaları ilgili mi? Temel kural:

    6.5 İfadeler

    1. Bir önceki ve bir sonraki sıra noktası arasında, bir nesne en fazla bir defa değiştirilmiş olan saklı değerine sahip olacaktır.Bir ifadenin değerlendirilmesiyle. Ayrıca, önceki değer yalnızca depolanacak değeri belirlemek için okunur.

    a[2] = 1 bir ifadedir, ancak ilklendirme değildir.

    Bu, Ek J ile biraz çelişmektedir:

    J.2 Tanımsız davranış

    • İki dizi noktası arasında, bir nesne bir kereden fazla modifiye edilir veya modifiye edilir ve depolanacak değeri belirlemekten başka bir değer okunur (6.5).

    Ek J, sadece ifadelerdeki değişikliklerin değil, değişikliklerin sayıldığını söylüyor. Ancak eklerin normatif olmadığı düşünüldüğünde, muhtemelen bunu görmezden gelebiliriz.

  • Alt nesne başlatmaları, başlatıcı ifadelerine göre nasıl sıralanır? Tüm başlatıcılar ilk önce (bazı sıralarda) değerlendiriliyor mu, sonra alt nesneler sonuçlarla başlatılıyor mu (başlatıcı listesi sırasına göre)? Yoksa araya girebilirler mi?


Sanırım int a[5] = { a[2] = 1 } şu şekilde yürütüldü:

  1. a için depolama, içerdiği blok girildiğinde tahsis edilir. İçerik bu noktada belirsizdir.
  2. (Sadece) başlatıcı çalıştırılır (a[2] = 1), ardından bir sıralama noktası izlenir. Bu, 1 öğesini a[2] içinde saklar ve 1 değerini döndürür.
  3. 1, a[0] (ilk başlatıcı ilk alt hedefi başlatır) başlatmak için kullanılır.

Fakat burada işler bulanıklaşıyor çünkü kalan öğeler (a[1], a[2], a[3], a[4]] 0 olarak başlatılmalı, ancak net değil. ne zaman: a[2] = 1 değerlendirilmeden önce olur mu? Öyleyse, a[2] = 1 "kazanır" ve a[2] değerinin üzerine yazardı, ancak bu atamanın tanımsız davranışı olur mu? Sıra noktaları uygun mu (yukarı bakın)? Yoksa tüm başlatıcılar değerlendirildikten sonra sıfır başlatma mı oluyor? Öyleyse, a[2], 0 olmalıdır.

C standardı burada ne olduğunu açıkça tanımlayamadığından, davranışın tanımsız olduğuna inanıyorum (ihmal ederek).

94
melpomene

Anlamıyorum, neden a[0], 1 yerine 0 yazdırıyor?

Muhtemelen a[2]=1, ilk önce a[2] öğesini başlatır ve ifadenin sonucu a[0] öğesini başlatmak için kullanılır.

N2176'dan (C17 taslağı):

6.7.9 Başlatma

  1. Başlatma listesi ifadelerinin değerlendirmeleri, belirsiz bir şekilde, l.'e göre sıralanır. birbiri ve dolayısıyla herhangi bir yan etkinin meydana geldiği sıra belirtilmemiş.  154)

Böylece 1 0 0 0 0 çıktısı da mümkün olabilirdi.

Sonuç: Anında başlatılan değişkeni değiştiren başlatıcıları yazmayın.

22
user694733

C11 standardının bu davranışı kapsadığını ve sonucun belirtilmemiş olduğunu ve C18’in bu alanda herhangi bir ilgili değişiklik yaptığını sanmadığını düşünüyorum.

Standart dilin ayrıştırılması kolay değildir. Standardın ilgili kısmı §6.7.9 Başlatma . Sözdizimi şu şekilde belgelenmiştir:

initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designationopt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier

Terimlerden birinin atama ifadesi olduğunu ve a[2] = 1 şüphesiz bir atama ifadesi olduğundan, dizilerin başlatıcılarının içine alınmasına izin verildiğini unutmayın. statik olmayan süre:

§4 Statik veya iş parçacığı saklama süresine sahip bir nesne için başlatıcıdaki tüm ifadeler sabit ifadeler veya dizgi değişmezleri olacaktır.

Anahtar paragraflardan biri:

§19 Başlatma, başlatıcı listesi sırasına göre gerçekleştirilir; her başlatıcı, aynı alt nesne için daha önce listelenen başlatıcıları geçersiz kılan belirli bir alt nesne için sağlanır;151) açıkça başlatılmayan tüm alt nesneler, statik depolama süresine sahip nesnelerle aynı şekilde başlatılmalıdır.

151) Alt nesne için geçersiz kılınan ve bu alt nesneyi başlatmak için kullanılmayan herhangi bir başlatıcı hiç değerlendirmeye alınmayabilir.

Ve başka bir anahtar paragraf:

§23 Başlatma listesi ifadelerinin değerlendirmeleri, birbirlerine göre belirsiz bir şekilde sıralanır ve böylece herhangi bir yan etkinin ortaya çıkma sırası belirtilmez.152)

152) Özellikle, değerlendirme sırasının, alt nesne başlatma sırasındaki ile aynı olması gerekmez.

§23 paragrafının, sorudaki notun şu olduğuna işaret ettiğinden oldukça eminim:

int a[5] = { a[2] = 1 };

belirtilmemiş davranışa yol açar. a[2] 'a yapılan atama bir yan etkidir ve ifadelerin değerlendirme sırası birbirlerine göre sırayla dizilir. Sonuç olarak, standarda itiraz etmenin ve belirli bir derleyicinin bunu doğru ya da yanlış kullandığını iddia etmenin bir yolu olduğunu sanmıyorum.

6
Jonathan Leffler

Benim Anlayışım ise a[2]=1 1 değerini döndürür. 

int a[5]={a[2]=1} --> int a[5]={1}

int a[5]={1} a [0] = 1 için değer atama 

Bu nedenle 1 için a [0]

Örneğin 

char str[10]={‘H’,‘a’,‘i’};


char str[0] = ‘H’;
char str[1] = ‘a’;
char str[2] = ‘i;
2
Karthika

Bulmaca için kısa ve basit bir cevap vermeye çalışıyorum: int a[5] = { a[2] = 1 };

  1. İlk a[2] = 1 ayarlandı. Bu, dizinin söylediği anlamına gelir: 0 0 1 0 0
  2. Ancak, diziyi sırayla başlatmak için kullanılan { } parantezinde yaptığınız için, ilk değeri (1 olan) alır ve bunu a[0] olarak ayarlar. Sanki int a[5] = { a[2] }; kalmış, sanki a[2] = 1 zaten var. Sonuçta ortaya çıkan dizi şudur: 1 0 1 0 0

Başka bir örnek: int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 }; - Sipariş biraz keyfi olsa da, soldan sağa doğru gittiğini varsayarsak, bu 6 adımda gerçekleşir:

0 0 0 1 0 0
1 0 0 1 0 0
1 0 0 1 2 0
1 2 0 1 2 0
1 2 0 1 2 3
1 2 3 1 2 3
1
Battle

a[2]= 1 ataması, 1 değerine sahip bir ifadedir ve esasen int a[5]= { 1 }; yazdınız (a[2]'a 1 atanması gibi bir yan etkiye sahip).

0
Yves Daoust