Cout << a ++ << a ;? için doğru cevap nedir?


98

Son zamanlarda bir röportajda aşağıdaki nesnel tipte bir soru vardı.

int a = 0;
cout << a++ << a;

Yanıtlar:

a. 10
b. 01
c. tanımlanmamış davranış

B seçeneğini yanıtladım, yani çıktı "01" olacaktır.

Ancak daha sonra bir görüşmeci tarafından bana sürpriz olarak doğru cevabın c: tanımsız seçeneği olduğu söylendi.

Şimdi, C ++ 'daki sıra noktaları kavramını biliyorum. Davranış aşağıdaki ifade için tanımsızdır:

int i = 0;
i += i++ + i++;

ama benim deyimi için anlayış uyarınca cout << a++ << a, ostream.operator<<()ilk iki kez, adını alacak ostream.operator<<(a++)ve daha sonra ostream.operator<<(a).

VS2010 derleyicisindeki sonucu da kontrol ettim ve çıktısı da '01'.


30
Bir açıklama istedin mi? Potansiyel adaylarla sık sık röportaj yapıyorum ve soru almakla oldukça ilgileniyorum, ilgi gösteriyor.
Brady

3
@jrok Tanımlanmamış bir davranış. Uygulamanın yaptığı her şey (patronunuza adınıza aşağılayıcı bir e-posta göndermek dahil) uygundur.
James Kanze

2
Bu soru, sıra noktalarından bahsetmeyen bir C ++ 11 (C ++ 'ın mevcut sürümü) cevabı için haykırıyor. Maalesef, C ++ 11'deki sıra noktalarının değiştirilmesi konusunda yeterince bilgili değilim.
CB Bailey

3
Tanımsız 10olmasaydı, kesinlikle olamazdı , ya ya 01da olurdu 00. ( c++Daima değere değerlendirecek cvardı önce arttıralım). Ve tanımlanmamış olmasa bile yine de korkunç derecede kafa karıştırıcı olurdu.
38'de sol tarafta

2
Biliyorsunuz, “cout << c ++ << c” başlığını okuduğumda, bir an bunu C ve C ++ dilleri ve “cout” adlı bir başkası arasındaki ilişki hakkında bir ifade olarak düşündüm. Biliyorsunuz, birisi "cout" un C ++ 'dan çok daha aşağı olduğunu ve C ++' nın C'den çok daha aşağı olduğunu düşündüklerini söylüyormuş gibi - ve muhtemelen geçişkenlik nedeniyle "cout" C'den çok, çok daha düşüktü :)
tchrist

Yanıtlar:


145

Düşünebilirsin:

cout << a++ << a;

Gibi:

std::operator<<(std::operator<<(std::cout, a++), a);

C ++, önceki değerlendirmelerin tüm yan etkilerinin sıra noktalarında gerçekleştirileceğini garanti eder . Fonksiyon argümanları değerlendirmesi arasında sıra noktası yoktur, bu da argümanın argümandan aönce std::operator<<(std::cout, a++)veya sonra değerlendirilebileceği anlamına gelir . Yani yukarıdakilerin sonucu tanımsızdır.


C ++ 17 güncellemesi

C ++ 17'de kurallar güncellendi. Özellikle:

Bir vardiya operatörü ifadesinde E1<<E2ve E1>>E2her değer hesaplaması ve yan etkisi, E1her değer hesaplamasından ve yan etkisinden önce sıralanır E2.

Bu, kodun sonuç üretmesini gerektirdiği anlamına gelir b, bu da çıktı verir 01.

Daha fazla ayrıntı için P0145R3 Deyimsel C ++ için İfade Değerlendirme Sırasını İyileştirme bölümüne bakın.


@Maxim: Açıklama için teşekkürler. Aldığın aramalarla, tanımsız bir davranış olurdu. Ama şimdi, bir sorum daha var (belki daha aptal olabilir ve temel bir şeyi kaçırıyorum ve yüksek sesle düşünüyorum) std :: operator << () 'nin global versiyonunun ostream :: operator <yerine çağrılacağını nasıl anladınız? <() üye sürümü. Hata ayıklamada global sürüm yerine ostream :: operator << () çağrısının üye sürümüne iniyorum ve başlangıçta cevabın 01 olacağını düşünmemin nedeni bu
26'da pravs

@Maxim Farklı bir şey yapmaz, ama ctürü intolduğu için operator<<burada üye fonksiyonlar vardır.
James Kanze

2
@pravs: operator<<bir üye işlev mi yoksa serbest duran bir işlev mi, sıra noktalarını etkilemez.
Maxim Egorushkin

11
'Sıra noktası' artık C ++ standardında kullanılmamaktadır. Belirsizdi ve 'önce sıralı / sonra sıralı' ilişkisiyle değiştirildi.
Rafał Dowgird

2
So the result of the above is undefined.Açıklamanız için sadece iyidir belirtilmemiş , değil tanımsız . JamesKanze bunun daha ezici nasıl izah tanımlanmamış olsa onun cevabını .
Deduplicator

68

Teknik olarak, genel olarak bu Tanımlanmamış Davranış'dır .

Ancak cevabın iki önemli yönü var.

Kod ifadesi:

std::cout << a++ << a;

şu şekilde değerlendirilir:

std::operator<<(std::operator<<(std::cout, a++), a);

Standart, bir işleve yönelik argümanların değerlendirme sırasını tanımlamaz.
Bu yüzden ya:

  • std::operator<<(std::cout, a++) önce değerlendirilir veya
  • aönce değerlendirilir veya
  • herhangi bir uygulama tanımlı sıra olabilir.

Bu sipariş, standarda göre Belirtilmemiş [Ref 1] 'dir.

[Ref 1] C ++ 03 5.2.2 İşlev çağrısı
Para 8

Argümanların değerlendirme sırası belirtilmemiştir . Bağımsız değişken ifade değerlendirmelerinin tüm yan etkileri, fonksiyon girilmeden önce yürürlüğe girer. Postfix ifadesinin ve bağımsız değişken ifade listesinin değerlendirme sırası belirtilmemiş.

Ayrıca, bir fonksiyon için argümanların değerlendirilmesi arasında bir sıra noktası yoktur, ancak bir sıra noktası yalnızca tüm argümanların değerlendirilmesinden sonra mevcuttur [Ref 2] .

[Ref 2] C ++ 03 1.9 Program yürütme [giriş.yürütme]: 17. paragraf
:

Bir fonksiyonu çağırırken (fonksiyon satır içi olsun veya olmasın), tüm fonksiyon argümanlarının (varsa) değerlendirilmesinden sonra, fonksiyon gövdesindeki herhangi bir ifade veya ifadenin yürütülmesinden önce gerçekleşen bir sıra noktası vardır.

Burada değerine, caraya giren bir sıra noktası olmadan birden fazla kez erişildiğine dikkat edin, bununla ilgili olarak standart şunu söylüyor:

[Ref 3] C ++ 03 5 İfadeler [ifade]: 4. paragraf
:

....
Önceki ve sonraki sıra noktası arasında, bir skaler nesnenin depolanmış değeri, bir ifadenin değerlendirilmesiyle en fazla bir kez değiştirilecektir. Ayrıca, önceki değere yalnızca depolanacak değeri belirlemek için erişilecektir . Bu paragrafın gereklilikleri, bir tam ifadenin alt ifadelerinin izin verilen her sıralaması için karşılanacaktır; aksi takdirde davranış tanımsızdır .

Kod, csıra noktası araya girmeden birden fazla değişiklik yapar ve saklanan nesnenin değerini belirlemek için ona erişilmez. Bu, yukarıdaki hükmün açık bir ihlalidir ve dolayısıyla standart tarafından zorunlu kılınan sonuç, Tanımlanmamış Davranış [Ref 3] 'dür .


1
Teknik olarak, davranış tanımsızdır, çünkü bir nesnede değişiklik vardır ve ona başka bir yerden müdahale eden bir sıra noktası olmadan erişilir. Tanımlanmamış olan değil belirtilmemiş; uygulamayı daha da fazla serbest bırakır.
James Kanze

1
@ Evet. Düzenlemelerinizi görmemiştim (jrok'un programın tuhaf bir şey yapamayacağı yönündeki ifadesine tepki göstermeme rağmen - yapabilir). Düzenlenmiş versiyonunuz gittiği yere kadar iyidir, ancak aklımda anahtar kelime kısmi sıralamadır ; sıra noktaları yalnızca kısmi bir sıralama sağlar.
James Kanze

1
@ Ayrıntılı bir açıklama için teşekkürler, gerçekten çok yardımcı !!
pravs

4
Yeni C ++ 0x standardı esasen aynı, ancak farklı bölümlerde ve farklı ifadelerde :) Alıntı: (1.9 Program Yürütme [intro.execution], par 15): "Bir skaler nesne üzerindeki bir yan etki, ya aynı skaler nesne üzerinde başka bir yan etki ya da aynı skaler nesnenin değerini kullanan bir değer hesaplaması, davranış tanımlanmamıştır. "
Rafał Dowgird

2
Bu cevapta bir hata olduğuna inanıyorum. "std :: cout << c ++ << c;" "std :: operatör << (std :: operatör << (std :: cout, c ++), c)" biçimine çevrilemez, çünkü std :: operatör << (std :: ostream &, int) mevcut değildir. Bunun yerine, "std :: cout.operator << (c ++). Operator (c);" 'ye çevirir, ki bu gerçekte "c ++" ve "c" değerlendirmesi arasında bir sıra noktasına sahiptir (aşırı yüklenmiş bir operatör bir işlev çağrısı ve dolayısıyla işlev çağrısı döndüğünde bir sıra noktası vardır). Sonuç olarak davranış ve uygulama düzeni olup , belirtilen.
Christopher Smith

20

Sıra noktaları yalnızca kısmi bir sıralamayı tanımlar . Sizin durumunuzda (aşırı yük çözümü tamamlandıktan sonra):

std::cout.operator<<( a++ ).operator<<( a );

Arasında bir dizi nokta vardır a++ve ilk çağrı std::ostream::operator<<ve ikinci arasında bir dizi nokta vardır ave ikinci çağrı std::ostream::operator<<, ancak herhangi bir dizi nokta arasında olduğu a++ve a; yegane sipariş kısıtlamaları a++, ilk çağrıdan önce tam olarak değerlendirilmesi (yan etkiler dahil) operator<<ve ikincisinin aikinci çağrıdan önce tam olarak değerlendirilmesidir operator<<. (Ayrıca nedensel sıralama kısıtlamaları da vardır: ikinci çağrı operator<<, bir argüman olarak ilkinin sonuçlarını gerektirdiğinden, birinciden önce olamaz.) §5 / 4 (C ++ 03) şunu belirtir:

Belirtilenler dışında, tek tek operatörlerin işlenenlerinin değerlendirme sırası ve tek tek ifadelerin alt ifadeleri ve yan etkilerin meydana gelme sırası belirtilmemiştir. Önceki ve sonraki sıra noktası arasında, bir skaler nesnenin depolanmış değeri, bir ifadenin değerlendirilmesiyle en fazla bir kez değiştirilecektir. Ayrıca, önceki değere yalnızca depolanacak değeri belirlemek için erişilecektir. Bu paragrafın gereklilikleri, bir tam ifadenin alt ifadelerinin izin verilen her sıralaması için karşılanacaktır; aksi takdirde davranış tanımsızdır.

İfadenin izin verilen sıralamaların biri a++, a, ilk çağrı operator<<için ikinci çağrı operator<<; bu, a( a++) ' nin saklanan değerini değiştirir ve yeni değeri (ikinci a) belirlemenin dışında ona erişir , davranış tanımsızdır.


Standart teklifinizden bir şey. "Aksi belirtilmedikçe" IIRC, aşırı yüklenmiş bir operatörle uğraşırken, operatörü bir işlev olarak ele alan ve bu nedenle std :: ostream :: operator << (int ). Yanlışım varsa lütfen düzelt.
Christopher Smith

@ChristopherSmith Aşırı yüklenmiş bir operatör, bir işlev çağrısı gibi davranır. Eğer ctanımlı bir kullanıcı ile kullanıcı tipi olduğunu ++yerine, intsonuçlar belirtilmemiş olurdu, ama hiçbir tanımsız davranış olmazdı.
James Kanze

1
İkisi arasında bir dizi noktaya bakın do @ChristopherSmith ciçinde foo(foo(bar(c)), c)? İşlevler çağrıldığında ve geri döndüklerinde bir sıra noktası vardır, ancak ikisinin değerlendirmeleri arasında işlev çağrısına gerek yoktur c.
James Kanze

1
@ChristopherSmith cBir UDT olsaydı , aşırı yüklenmiş operatörler işlev çağrıları olurdu ve bir sıra noktası eklerlerdi, böylece davranış tanımsız olmazdı. Ancak, alt ifadenin cönce mi yoksa sonra c++mı değerlendirildiği hala belirsiz olacaktır , bu nedenle artırılmış sürümü alıp almadığınız belirtilmeyecektir (ve teoride her seferinde aynı olması gerekmeyecektir).
James Kanze

1
@ChristopherSmith Sıra noktasından önceki her şey, sıra noktasından sonraki her şeyden önce olacaktır. Ancak sıra noktaları yalnızca kısmi bir sıralamayı tanımlar. Söz konusu ekspresyon, örneğin, orada alt ifadeler arasında dizi nokta cve c++iki herhangi bir sırada ortaya çıkabilir, bu yüzden. Noktalı virgüllere gelince ... Yalnızca tam ifadeler oldukları sürece bir sıra noktasına neden olurlar. : Diğer önemli dizisi noktaları işlev çağrısı olan f(c++)artırılır göreceksiniz cyılında f, ve virgülle operatörü&& , ||ve ?:ayrıca dizi noktaları neden olur.
James Kanze

4

Doğru cevap soruyu sorgulamaktır. Bu ifade kabul edilemez çünkü okuyucu net bir cevap göremez. Buna bakmanın bir başka yolu da, ifadenin yorumlanmasını çok daha zor hale getiren yan etkiler (c ++) sunduğumuzdur. Kısa ve öz kod, anlamı açık olduğu sürece harikadır.


4
Soru, zayıf bir programlama uygulaması (ve hatta geçersiz C ++) gösterebilir. Ancak bir yanıtın neyin yanlış olduğunu ve neden yanlış olduğunu gösteren soruyu yanıtlaması gerekiyor . Soruya yapılan bir yorum, tamamen geçerli olsa bile bir cevap değildir. En iyi ihtimalle, bu bir cevap değil, bir yorum olabilir.
PP
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.