C'de dizi başlatma hakkında kafa karışıklığı


102

C dilinde, şöyle bir dizi başlatırsanız:

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

bu durumda, dizinin açıkça başlatılmamış tüm öğeleri örtük olarak sıfırlarla başlatılacaktır.

Ama şö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 yerine a[0]yazdırıyor ? Tanımlanmamış bir davranış mı?10

Not: Bu soru bir röportajda sorulmuştur.


35
İfade olarak a[2]=1değerlendirilir 1.
tkausl

14
Çok derin bir soru. Görüşmeyi yapan kişinin cevabı kendisinin bilip bilmediğini merak ediyorum. Yapmıyorum. Aslında, görünüşte ifadenin a[2] = 1değeri 1, ancak belirlenmiş bir başlatıcı ifadesinin sonucunu ilk öğenin değeri olarak almanıza izin verilip verilmediğinden emin değilim. Avukat etiketini eklemiş olmanız, standardı belirten bir cevaba ihtiyacımız olduğunu düşünüyorum.
Bathsheba

15
Eğer bu onların favori soruysa, bir kurşun atlatmış olabilirsin. Şahsen ben yazılı bir programlama alıştırmasının (bir derleyiciye ve hata ayıklayıcıya erişimli) yukarıdaki gibi "ace" tarzı sorular yerine birkaç saat içinde alınmasını tercih ederim. Bir cevabı tahmin edebilirim , ama bunun gerçek bir temeli olacağını sanmıyorum.
Bathsheba

1
@Bathsheba Tam tersini yapardım, çünkü buradaki cevap şimdi her iki soruyu da cevaplıyor.
Hoşçakal SE

1
@Bathsheba en iyisi olurdu. Yine de konuya geldiğinde OP'ye soru için övgü verirdim. Ancak bu, ne hissettiğime "doğru şey" olacağına karar vermek bana göre değil.
Hoşçakal SE

Yanıtlar:


95

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

İşin komik yanı, bana mantıklı gelen tek parçanın sorduğunuz kısım olmasıdır: atama operatörü atanan değeri döndürdüğü için a[0]ayarlanır 1. Belirsiz olan diğer her şey.

Kod olsaydı int a[5] = { [2] = 1 }, her şey kolay olurdu: Bu ayar başlatıcısı belirlenmiş bulunuyor a[2]etmek 1ve her şey 0. Ancak, { a[2] = 1 }bir atama ifadesi içeren belirlenmiş olmayan bir başlatıcımız var ve bir tavşan deliğine düşüyoruz.


İşte şimdiye kadar bulduklarım:

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

    6.7.8 Başlatma

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

    a[2] = 1sabit bir ifade değildir, bu nedenle aotomatik depolamaya sahip olmalıdır.

  • a kendi başlatması kapsamındadır.

    6.2.1 Tanımlayıcıların kapsamı

    1. Yapı, birleşim ve numaralandırma etiketleri, etiketi bildiren bir tür tanımlayıcısında etiketin görünmesinden hemen sonra başlayan kapsama sahiptir. Her numaralandırma sabiti, bir numaralandırıcı listesinde tanımlayıcı numaralandırıcısının görünmesinden hemen sonra başlayan bir kapsama sahiptir. Diğer herhangi bir tanımlayıcı, bildiricinin tamamlanmasından hemen sonra başlayan bir kapsama sahiptir.

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

  • a kendi başlangıcında yaşıyor.

    6.2.4 Nesnelerin saklama süreleri

    1. Olan tanımlayıcı bir bağlantı ve depolama sınıfı belirleyici olmayan bildirilen bir amacı staticvardır otomatik depolama süresi .

    2. Değişken uzunluklu dizi tipine sahip olmayan böyle bir nesne için , yaşam süresi, bu bloğun yürütülmesi herhangi bir şekilde sona erene kadar, girişten ilişkili olduğu bloğa kadar uzanır . (Kapalı bir bloğun girilmesi veya bir işlevin çağrılması, geçerli bloğun yürütülmesini askıya alır, ancak sona erdirmez.) Blok yinelemeli olarak girilirse, her seferinde nesnenin yeni bir örneği oluşturulur. Nesnenin başlangıç ​​değeri belirsizdir. Nesne için bir başlatma belirtilmişse, bloğun yürütülmesinde bildirime her ulaşıldığında gerçekleştirilir; aksi takdirde, beyana her ulaşıldığında değer belirsiz hale gelir.

  • Sonra bir sıra noktası var a[2]=1.

    6.8 İfadeler ve bloklar

    1. Bir tam ifade başka ekspresyon veya bir Bildiricisi bir parçası olmayan bir ifadesidir. Aşağıdakilerin her biri tam bir ifadedir: bir başlatıcı ; bir ifade ifadesindeki ifade; bir seçim ifadesinin ( ifveya switch) kontrol edici ifadesi ; bir whileveya doifadenin kontrol edici ifadesi; bir ifadenin (isteğe bağlı) ifadelerinin her biri for; bir ifadedeki (isteğe bağlı) ifade return. Tam ifadenin sonu bir sıra noktasıdır.

    Örneğin int foo[] = { 1, 2, 3 }, { 1, 2, 3 }parçada, her biri kendisinden sonra bir sıra noktasına sahip olan, köşeli ayraçlı başlatıcıların listesi vardır.

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

    6.7.8 Başlatma

    1. Küme ayracı ile çevrili her başlatıcı listesinin ilişkili bir geçerli nesnesi vardır . Hiçbir atama olmadığında, mevcut nesnenin alt nesneleri, mevcut nesnenin türüne göre başlatılır: artan alt simge sırasındaki dizi öğeleri, bildirim sırasındaki yapı üyeleri ve bir birleşimin ilk adlandırılmış üyesi. [...]

     

    1. Başlatma, başlatıcı listesi sırasında gerçekleşir, her başlatıcı belirli bir alt nesne için sağlanır ve aynı alt nesne için daha önce listelenen başlatıcıları geçersiz kılar; Açıkça başlatılmayan tüm alt nesneler, statik depolama süresine sahip nesnelerle aynı şekilde örtülü olarak başlatılacaktır.
  • Ancak, başlatıcı ifadelerinin sırayla değerlendirilmesi gerekmez.

    6.7.8 Başlatma

    1. Başlatma listesi ifadeleri arasında herhangi bir yan etkinin oluşma sırası belirtilmemiştir.

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

  • Sıra noktaları alakalı mı? Temel kural şudur:

    6.5 İfadeler

    1. Önceki ve sonraki sıra noktası arasında, bir nesnenin saklanan değeri , bir ifadenin değerlendirilmesiyle en fazla bir kez değiştirilmelidir . Ayrıca, önceki değer yalnızca depolanacak değeri belirlemek için okunmalıdır.

    a[2] = 1 bir ifadedir, ancak başlatma değildir.

    Bu, Ek J ile biraz çelişmektedir:

    J.2 Tanımlanmamış davranış

    • İki sıra noktası arasında, bir nesne birden fazla değiştirilir veya değiştirilir ve saklanacak değeri belirlemenin dışında önceki değer okunur (6.5).

    Ek J, sadece ifadelerle yapılan değişikliklerin değil, herhangi bir değişikliğin önemli olduğunu söylüyor. Ancak eklerin normatif olmadığı düşünülürse, muhtemelen bunu görmezden gelebiliriz.

  • Başlatıcı ifadelerine göre alt nesne başlatmaları nasıl sıralanır? İlk önce tüm başlatıcılar değerlendiriliyor mu (bazı sırayla), sonra alt nesneler sonuçlarla (başlatıcı listesi sırasına göre) başlatılıyor mu? Yoksa araya eklenebilirler mi?


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

  1. Depolama a, içeren bloğu girildiğinde tahsis edilir. İçerik bu noktada belirsizdir.
  2. (Sadece) başlatıcı çalıştırılır ( a[2] = 1) ve ardından bir sıra noktası gelir. Bu depolar 1içinde a[2]ve iadeler 1.
  3. Bu 1, başlatmak için kullanılır a[0](ilk başlatıcı, ilk alt nesneyi başlatır).

Ama burada işler kalan elementler (çünkü bulanık olsun a[1], a[2], a[3], a[4]) başlatılması gerekiyordu 0, ama ne zaman açık değil: Daha önce de oldu mu a[2] = 1değerlendirilir? Eğer öyleyse, a[2] = 1"kazanır" ve üzerine yazılır a[2], ancak bu atamanın tanımsız bir davranışı olur mu çünkü sıfır başlatma ile atama ifadesi arasında sıra noktası yoktur? Sıra noktaları alakalı mı (yukarıya bakın)? Yoksa tüm başlatıcılar değerlendirildikten sonra sıfır başlatma mı gerçekleşir? Eğer öyleyse, a[2]sona ermeli 0.

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


1
Tanımsız yerine bunun belirtilmemiş olduğunu ve bu durumun uygulamaların yorumlanmasına açık olduğunu iddia ediyorum .
programcı bir dostum

1
"bir tavşan deliğine düşüyoruz" LOL! Bunu bir UB veya belirtilmemiş bir şey için hiç duymadım.
BЈовић

2
@Someprogrammerdude Bunun belirlenmemiş olabileceğini düşünmüyorum (" bu Uluslararası Standardın iki veya daha fazla olasılık sağladığı ve herhangi bir durumda seçildiği başka hiçbir şartı getirmediği davranış ") çünkü standart, aralarında gerçekten herhangi bir olasılık sunmuyor. Seç. Bu sadece ben "kapsamına giren inanıyoruz ki, ne olur demiyor . Tanımsız davranıştır [...] davranışlarıyla ilgili bir açık tanımının çıkarılması ile bu standardın [...] belirtilen "
Melpomene

2
@ BЈовић Bu aynı zamanda sadece tanımlanmamış davranış için değil, aynı zamanda açıklanması için buna benzer bir iş parçacığına ihtiyaç duyan tanımlanmış davranış için de çok güzel bir açıklama.
gnasher729

1
@JohnBollinger Aradaki fark, a[0]alt nesneyi başlatıcısını değerlendirmeden önce gerçekten başlatamazsınız ve herhangi bir başlatıcıyı değerlendirirken bir sıra noktası içerir (çünkü bu bir "tam ifade" dir). Bu nedenle, başlattığımız alt nesneyi değiştirmenin adil bir oyun olduğuna inanıyorum.
melpomene

22

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

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

N2176'dan (C17 taslağı):

6.7.9 Başlatma

  1. Başlatma listesi ifadelerinin değerlendirmeleri, birbirlerine göre belirsiz bir şekilde sıralanır ve bu nedenle, herhangi bir yan etkinin meydana gelme sırası belirtilmez. 154)

Yani çıktı 1 0 0 0 0da mümkün olacakmış gibi görünüyordu .

Sonuç: İlklendirilmiş değişkeni anında değiştiren başlatıcılar yazmayın.


1
Bu kısım geçerli değildir: Burada yalnızca bir başlatıcı ifadesi vardır, bu nedenle herhangi bir şeyle sıralanmasına gerek yoktur.
melpomene

@melpomene yoktur {...}başlatır ifade a[2]etmek 0ve a[2]=1başlatır alt ifadesi a[2]için 1.
user694733

1
{...}çaprazlı bir başlatıcı listesidir. Bu bir ifade değil.
melpomene

@melpomene Tamam, orada olabilirsiniz. Ama yine de, paragrafın geçerli olması için hala 2 rakip yan etkinin olduğunu iddia ediyorum.
user694733

@melpomene Sıralanacak iki şey vardır: ilk başlatıcı ve diğer öğelerin ayarı 0
MM

6

Bence C11 standardı bu davranışı kapsıyor ve sonucun belirsiz olduğunu söylüyor ve C18'in bu alanda ilgili herhangi bir değişiklik yaptığını düşünmüyorum.

Standart dilin ayrıştırılması kolay değildir. Standardın ilgili bölümü §6.7.9 Başlatma'dır . 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ğuna dikkat edin ve a[2] = 1şüphesiz bir atama ifadesi olduğundan, statik olmayan süreye sahip diziler için başlatıcıların içinde buna izin verilir:

§4 Statik veya iş parçacığı depolama süresi olan bir nesne için başlatıcıdaki tüm ifadeler, sabit ifadeler veya dizgi değişmezleri olmalıdır.

Anahtar paragraflardan biri şudur:

§19 Başlatma, başlatıcı listesi sırasında gerçekleşir, her başlatıcı belirli bir alt nesne için sağlanır ve aynı alt nesne için önceden listelenen başlatıcıları geçersiz kılar; 151) açıkça başlatılmayan tüm alt nesneler, statik depolama süresi olan nesnelerle aynı şekilde örtülü olarak başlatılacaktır.

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

Ve bir diğer önemli paragraf:

§23 Başlatma listesi ifadelerinin değerlendirmeleri, birbirlerine göre belirsiz bir şekilde sıralanır ve bu nedenle herhangi bir yan etkinin meydana gelme sırası belirtilmemiştir. 152)

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

Kesinlikle eminim ki §23 paragraf sorudaki gösterimin:

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

belirsiz davranışlara yol açar. Atama a[2]bir yan etkidir ve ifadelerin değerlendirme sırası birbirine göre belirsiz şekilde sıralanır. Sonuç olarak, standarda başvurmanın ve belirli bir derleyicinin bunu doğru veya yanlış şekilde ele aldığını iddia etmenin bir yolu olduğunu düşünmüyorum.


Yalnızca bir ilklendirme listesi ifadesi vardır, bu nedenle §23 ilgili değildir.
melpomene

2

Anlayışım 1a[2]=1 değerini döndürür , bu nedenle kod

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

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

Dolayısıyla [0] için 1 yazdırır

Örneğin

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


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

2
Bu bir [dil-avukat] sorusudur, ancak bu standartla çalışan bir cevap değildir, dolayısıyla konuyla ilgisiz hale gelir. Ayrıca 2 çok daha derinlemesine cevap mevcut ve cevabınız hiçbir şey eklemiyor gibi görünüyor.
Hoşçakal SE

Şüphem var, göndermiş olduğum konsept yanlış mı, beni bununla açıklayabilir misin?
Karthika

1
Standardın ilgili bölümlerinde zaten verilmiş çok iyi bir cevap varken, sadece nedenlerle spekülasyon yaparsınız. Sorunun konusu sadece bunun nasıl olabileceğini söylemek değil. Bu, standardın ne olması gerektiğini söylediği ile ilgilidir.
Hoşçakal SE

Ama yukarıdaki soruyu gönderen kişi nedenini sordu ve neden oluyor? Bu yüzden sadece bu cevabı bıraktım ama kavram doğru.
Karthika

OP " Tanımlanmamış bir davranış mı? " Diye sordu . Cevabınız söylemiyor.
melpomene

1

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

  1. İlk a[2] = 1ayarlandı. Bu, dizinin şunu söylediği anlamına gelir:0 0 1 0 0
  2. Ancak { }, diziyi sırayla başlatmak için kullanılan parantez içinde yaptığınıza göre, ilk değeri (olan 1) alır ve bunu olarak ayarlar a[0]. Sanki int a[5] = { a[2] };zaten var olduğumuz yerde kalacakmış gibi a[2] = 1. Ortaya çıkan dizi şimdi:1 0 1 0 0

Başka bir örnek: int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };- Sıra biraz keyfi olsa da, soldan sağa gittiğini varsayarsak, şu 6 adımda gidecektir:

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
A = B = C = 5bir bildirim (veya başlatma) değildir. Operatör doğru çağrışımlı A = (B = (C = 5))olduğu için ayrıştırılan normal bir ifadedir =. Bu, başlatmanın nasıl çalıştığını açıklamaya gerçekten yardımcı olmuyor. Dizi, tanımlandığı blok girildiğinde gerçekten var olmaya başlar, bu gerçek tanımın yürütülmesinden çok önce olabilir.
melpomene

1
" Soldan sağa doğru gidiyor, her biri dahili bildirimle başlıyor " yanlıştır. C standart açıkça "diyor herhangi bir yan etkisi başlatma liste ifadeleri arasında meydana sırası belirsizdir. "
Melpomene

1
" Örneğimdeki kodu yeterli sayıda test edersiniz ve sonuçların tutarlı olup olmadığına bakarsınız. " Bu böyle çalışmaz. Tanımlanmamış davranışın ne olduğunu anlamıyor gibisin. C'deki her şeyin varsayılan olarak tanımsız davranışı vardır; sadece bazı parçaların standart tarafından tanımlanan davranışa sahip olmasıdır. Bir şeyin tanımlanmış davranışa sahip olduğunu kanıtlamak için, standarda atıfta bulunmalı ve ne olması gerektiğini nerede tanımladığını göstermelisiniz. Böyle bir tanımın yokluğunda, davranış tanımsızdır.
melpomene

1
(1) numaralı noktadaki iddia, buradaki anahtar soru üzerinde muazzam bir sıçramadır: a [2] 'den 0'a örtük olarak ilklendirilmesi, a[2] = 1başlatıcı ifadesinin yan etkisi uygulanmadan önce mi gerçekleşir? Gözlemlenen sonuç sanki öyleydi, ancak standart durumun böyle olması gerektiğini belirtmiyor gibi görünüyor. Tartışmanın merkezi budur ve bu cevap onu tamamen görmezden gelmektedir.
John Bollinger

1
"Tanımlanmamış davranış", dar bir anlamı olan teknik bir terimdir. "Gerçekten emin olmadığımız davranış" anlamına gelmez. Buradaki temel kavrayış, hiçbir derleyiciye sahip olmayan hiçbir testin, belirli bir programın standarda göre iyi davrandığını veya olmadığını gösteremeyeceğidir , çünkü bir program tanımlanmamış davranışa sahipse, derleyicinin her şeyi yapmasına izin verilir - çalışma dahil tamamen tahmin edilebilir ve makul bir şekilde. Bu sadece derleyici yazarlarının bir şeyleri belgelediği bir uygulama kalitesi sorunu değildir - bu belirtilmemiş veya uygulama tanımlı bir davranıştır.
Jeroen Mostert

0

Atama a[2]= 1, değere sahip bir ifadedir 1ve siz aslında yazdınız int a[5]= { 1 };( aynı zamanda a[2]atanan yan etkiyle 1).


Ancak yan etkinin ne zaman değerlendirildiği net değildir ve davranış derleyiciye bağlı olarak değişebilir. Ayrıca standart, derleyiciye özgü gerçekleştirmeler için açıklamaların yararlı olmadığını, bunun tanımlanmamış bir davranış olduğunu belirtiyor gibi görünüyor.
Hoşçakal SE

@KamiKaze: Elbette, değer 1 oraya yanlışlıkla indi.
Yves Daoust

0

Bunun int a[5]={ a[2]=1 };bir programcının kendisini kendi ayağına vurması için iyi bir örnek olduğuna inanıyorum .

Ne demek istediğini, int a[5]={ [2]=1 };hangisinin C99 tarafından belirlenmiş başlatıcı ayar elemanı 2'ye 1 ve gerisinin sıfıra ayarlanması olduğunu düşünmek cazip gelebilir .

Gerçekten kastettiğiniz nadir durumlarda, int a[5]={ 1 }; a[2]=1;bu onu yazmanın komik bir yolu olurdu. Her neyse, burada bazıları write to a[2]yürütüldüğünde iyi tanımlanmadığına işaret etse de, kodunuzun özü budur. Buradaki tuzak, a[2]=1belirlenmiş bir başlatıcı değil, kendisi 1 değerine sahip basit bir atama olmasıdır.


Görünüşe göre bu dil-avukat konusu standart taslaklardan referanslar istiyor. Bu yüzden olumsuz oy verildiniz (gördüğünüz gibi ben de aynı sebepten olumsuz oy aldım). Bence yazdıklarınız tamamen iyi ama buradaki tüm dil avukatları ya komiteden ya da onun gibi bir şey gibi görünüyor. Bu yüzden hiç yardım istemiyorlar, taslağın davayı kapsayıp kapsamadığını kontrol etmeye çalışıyorlar ve buradaki adamların çoğu, onlara yardım ettiğiniz gibi cevap verirseniz tetikleniyor. Sanırım cevabımı sileceğim :) Bu konu kuralları açık bir şekilde ortaya
Abdurrahim
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.