Neden f (i = -1, i = -1) tanımlanmamış davranış?


267

Değerlendirme ihlallerinin sırası hakkında okuyordum ve beni şaşırtan bir örnek veriyorlar.

1) Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki başka bir yan etkiye göre sıralı değilse, davranış tanımsızdır.

// snip
f(i = -1, i = -1); // undefined behavior

Bu bağlamda, ia, skaler bir amacı , görünüşte araçlar

Aritmetik türler (3.9.1), numaralandırma türleri, işaretçi türleri, işaretçi-üye türleri (3.9.2), std :: nullptr_t ve bu türlerin (3.9.3) cv nitelikli sürümleri topluca skaler tipler olarak adlandırılır.

Bu durumda ifadenin nasıl belirsiz olduğunu anlamıyorum. Bana öyle geliyor ki, ilk ya da ikinci argüman ilk değerlendirilirse, isonlanır -1ve her iki argüman da değerlendirilir -1.

Birisi lütfen açıklığa kavuşturabilir mi?


GÜNCELLEME

Tüm tartışmaları gerçekten takdir ediyorum. Şimdiye kadar, @ harmic'in cevabını çok seviyorum, çünkü ilk bakışta ne kadar ileri göründüğüne rağmen bu ifadeyi tanımlamanın güçlüklerini ve karmaşıklıklarını ortaya koyuyor. @ acheong87 referansları kullanırken ortaya çıkan bazı sorunlara dikkat çekiyor, ancak bu sorunun sorulmamış yan etkiler yönüne dik olduğunu düşünüyorum.


ÖZET

Bu soru çok dikkat çektiğinden, ana noktaları / cevapları özetleyeceğim. İlk olarak, "neden" yakından "ne yani henüz ustaca farklı anlamlar ilgili olabileceği anlamına işaret etmek bana küçük anlatılanlar izin sebebi " ne, " nedeni " ne "ve amaç ". Yanıtları, "neden" olarak adlandırdıkları bu anlamlardan hangileriyle gruplandıracağım.

hangi sebepten dolayı

Buradaki ana cevap, Paul Draper'dan geliyor , Martin J de benzer ama kapsamlı olmayan bir cevaba katkıda bulunuyor. Paul Draper'ın yanıtı

Tanımlanmamış bir davranıştır, çünkü davranışın ne olduğu tanımlanmamıştır.

Cevap, C ++ standardının söylediklerini açıklamak açısından genel olarak çok iyi. Ayrıca, f(++i, ++i);ve gibi bazı UB vakalarını da ele alır f(i=1, i=-1);. İlk bağımsız değişken olmalıdır, alakalı durumlarda ilk olarak, açık değil i+1, ikinci i+2ya da tam tersi; ikincisinde, iişlev çağrısından sonra 1 veya -1 olması gerektiği açık değildir . Bu iki durum da UB'dir, çünkü aşağıdaki kurallara girerler:

Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki başka bir yan etkiye göre sıralanmamışsa, davranış tanımsızdır.

Bu nedenle, f(i=-1, i=-1)programcının amacı (IMHO) açık ve açık olmasına rağmen, aynı kuralın altına düştüğü için de UB'dir.

Paul Draper ayrıca,

Tanımlanmış bir davranış olabilir mi? Evet. Tanımlandı mı? Hayır.

bu da bizi "hangi nedenle / amaç için f(i=-1, i=-1)tanımsız davranış olarak kaldı?"

hangi nedenle / amaç için

C ++ standardında bazı gözetim (belki de dikkatsiz) olmasına rağmen, birçok ihmal iyi gerekçelendirilmiştir ve belirli bir amaca hizmet eder. Her ne kadar amacın ya "derleyici yazarın işini kolaylaştırmak" ya da "daha hızlı kod" olduğunu bilsem de, temelde UB olarak iyi bir neden olup olmadığını bilmekle ilgileniyordum f(i=-1, i=-1) .

Zararlı ve supercat , UB için bir neden sağlayan ana cevapları sağlar . Harmic, görünüşte atomik atama işlemlerini birden fazla makine talimatına ayırabilecek bir optimize edici derleyicinin ve optimum hız için bu talimatları daha fazla araya sokabileceğine dikkat çekiyor. Bu çok şaşırtıcı sonuçlara yol açabilir: isenaryosunda -2 olur! Bu nedenle, zararlı, aynı değerin bir değişkene birden çok kez atanmasının, işlemlerden etkilenmezse nasıl kötü etkileri olabileceğini gösterir.

supercat, f(i=-1, i=-1)olması gerektiği gibi görünmeye çalışmanın tuzaklarının ilgili bir açıklamasını sağlar . Bazı mimarilerde, aynı bellek adresine birden fazla eşzamanlı yazma işlemine karşı katı kısıtlamalar olduğuna dikkat çekiyor. Bir derleyici, daha az önemsiz bir şeyle uğraşırsak bunu yakalamakta zorlanabilir f(i=-1, i=-1).

davidf ayrıca zarar verenlere çok benzeyen serpiştirme talimatlarının bir örneğini sunar.

Zararlıların, süper kedilerin ve davidflerin örneklerinin her biri biraz tartışmalı olsa da, birlikte ele alındıklarında neden f(i=-1, i=-1)tanımsız davranışlar olması gerektiğine dair somut bir neden sunmaya devam ediyorlar .

Zararlıların cevabını kabul ettim, çünkü Paul Draper'ın cevabı "hangi sebepten ötürü" kısmına daha iyi hitap etmesine rağmen, nedenin tüm anlamlarını ele almak için en iyi işi yaptı.

diğer cevaplar

JohnB , aşırı yüklenmiş atama işleçlerini (yalnızca düz skalalar yerine) düşünürsek, o zaman da başımız belaya girebilir.


1
Skaler nesne skaler tipte bir nesnedir. Bkz. 3.9 / 9: "Aritmetik türler (3.9.1), numaralandırma türleri, işaretçi türleri, işaretçi - üye türleri (3.9.2) std::nullptr_t, ve bu türlerin (3.9.3) cv nitelikli sürümleri topluca skaler tipler olarak adlandırılır . "
Rob Kennedy

1
Belki sayfada bir hata vardır ve bunlar aslında f(i-1, i = -1)veya benzeri bir şey anlamına geliyordu .
Bay Lister


@RobKennedy Teşekkürler. "Aritmetik tipler" bool içerir mi?
Nicu Stiurca

1
SchighSchagh güncellemenizin cevap bölümünde olması gerekir.
Grijesh Chauhan

Yanıtlar:


343

İşlemler sıralanmamış olduğundan, ödevi gerçekleştiren talimatların araya girilemeyeceğini söyleyecek hiçbir şey yoktur. CPU mimarisine bağlı olarak bunu yapmak uygun olabilir. Başvurulan sayfa şunu belirtir:

A, B'den önce sıralanmazsa ve B, A'dan önce sıralanmazsa, iki olasılık vardır:

  • A ve B değerlendirmeleri değerlendirilmemiştir: herhangi bir sırayla gerçekleştirilebilir ve üst üste gelebilir (tek bir yürütme iş parçacığında, derleyici A ve B'yi içeren CPU talimatlarını araya ekleyebilir)

  • A ve B değerlendirmeleri belirsiz bir şekilde sıralanır: herhangi bir sırayla gerçekleştirilebilir, ancak üst üste gelmeyebilir: ya A, B'den önce tamamlanır ya da B, A'dan önce tamamlanır. değerlendirilir.

Tek başına bir soruna yol açıyormuş gibi görünmüyor - gerçekleştirilen işlemin -1 değerini bir bellek konumuna kaydettiği varsayılarak. Ancak derleyicinin bunu aynı etkiye sahip ayrı bir talimatlar kümesine optimize edemeyeceğini, ancak işlemin aynı bellek konumundaki başka bir işlemle araya sokulması durumunda başarısız olabileceğini söyleyecek hiçbir şey yoktur.

Örneğin, belleği sıfırlamanın daha verimli olduğunu düşünün, sonra -1 inç değerini yüklemeye kıyasla azaltın. Sonra bu:

f(i=-1, i=-1)

olabilir:

clear i
clear i
decr i
decr i

Şimdi i -2.

Muhtemelen sahte bir örnektir, ancak mümkündür.


59
Sıralama kurallarına uyurken ifadenin aslında beklenmedik bir şey nasıl yapabileceğine dair çok güzel bir örnek. Evet, biraz yapmacık, ama bu yüzden ilk etapta sorduğum kod kesildi. :)
Nicu Stiurca

10
Ödev atomik bir işlem olarak yapılsa bile, her iki atamanın aynı anda yapıldığı bir süperskalar mimari düşünmek, başarısızlıkla sonuçlanan bellek erişim çakışmasına neden olabilir. Dil, derleyici yazarlarının hedef makinenin avantajlarını kullanma konusunda mümkün olduğu kadar özgür olacak şekilde tasarlanmıştır.
ach

11
İki atamaları unsequenced çünkü gerçekten her iki parametre aynı değişkene aynı değeri atama bile nasıl senin örneğinde olduğu gibi ben beklenmedik bir sonuç neden olabilir
Martin J.

1
Derlenen kodun her zaman beklediğiniz gibi olmadığı + 1e + 6 (tamam, +1). Optimize
Corey

3
Arm işlemcisinde 32bit yük 4 talimat alabilir: load 8bit immediate and shift4 kata kadar çıkar. Genellikle derleyici bunu önlemek için tablodan bir sayı almak için dolaylı adresleme yapar. (-1 bir komutta yapılabilir, ancak başka bir örnek seçilebilir).
ctrl-alt-delor

208

İlk olarak, "skaler nesne" bir benzeri şekli anlamına int, floatya da bir işaretçi (bkz C ++ bir skaler Nesne nedir? ).


İkincisi, daha açık görünebilir

f(++i, ++i);

tanımlanmamış bir davranışa sahip olur. Fakat

f(i = -1, i = -1);

daha az açıktır.

Biraz farklı bir örnek:

int i;
f(i = 1, i = -1);
std::cout << i << "\n";

Hangi görev "son" i = 1, ya da oldu i = -1? Standartta tanımlanmamıştır. Gerçekten, bu iolabilir 5( bunun nasıl olabileceğine dair tamamen makul bir açıklama için zararın cevabına bakınız). Veya program segfault olabilir. Veya sabit diskinizi yeniden biçimlendirin.

Ama şimdi soruyorsunuz: "Örneğim ne olacak? Her -1iki atama için de aynı değeri ( ) kullandım . Bu konuda net olmayan ne olabilir?"

Haklısınız ... C ++ standartlar komitesinin bunu açıklaması dışında.

Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki başka bir yan etkiye göre sıralanmamışsa, davranış tanımsızdır.

Onlar olabilir özel durum için özel bir istisna yaptık, ama yapmadılar. (Ve neden? Kullanılması her zamankinden muhtemelen olurdu ne olacak? Olmalı) Yani, ihâlâ olabilir 5. Veya sabit diskiniz boş olabilir. Böylece sorunuzun cevabı:

Tanımlanmamış bir davranıştır, çünkü davranışın ne olduğu tanımlanmamıştır.

(Birçok programcı "tanımsız" ın "rastgele" veya "öngörülemez" anlamına geldiğini düşündüğünden bu vurgu gerektirir. Bu standart tarafından tanımlanmadığı anlamına gelir. Davranış% 100 tutarlı olabilir ve hala tanımsız olabilir.)

Tanımlanmış bir davranış olabilir mi? Evet. Tanımlandı mı? Hayır. Dolayısıyla "tanımsız" dır.

Bununla birlikte, "tanımsız" bir derleyicinin sabit sürücünüzü biçimlendireceği anlamına gelmez ... bunun olabileceği ve yine de standartlara uygun bir derleyici olacağı anlamına gelir . Gerçekçi olarak, eminim g ++, Clang ve MSVC hepsi beklediğinizi yapacak. Sadece "yapmak zorunda değiller".


Farklı bir soru şu olabilir: C ++ standartlar komitesi bu yan etkiyi neden eşsiz kılmayı seçti? . Bu cevap, komitenin tarihini ve görüşlerini içerecektir. Veya bu yan etkinin C ++ 'da sıralanmamış olması iyi mi? Standart komitenin fiili gerekçesi olsun ya da olmasın, herhangi bir gerekçeye izin verir. Bu soruları burada veya programmers.stackexchange.com adresinde sorabilirsiniz.


9
@hvd, evet, aslında -Wsequence-pointg ++ için etkinleştirirseniz sizi uyaracağını biliyorum.
Paul Draper

47
"Eminim g ++, Clang ve MSVC beklediğinizi yapar" Modern bir derleyiciye güvenmem. Onlar kötülük. Örneğin, bunun tanımsız bir davranış olduğunu fark edebilir ve bu koda ulaşılamaz olduğunu varsayabilirler. Bugün bunu yapmazlarsa, yarın da yapabilirler. Herhangi bir UB bir saatli bomba.
CodesInChaos

8
@BlacklightShining "cevabınız kötü çünkü iyi değil" çok yararlı değil, değil mi?
Vincent van der Weele

13
@BobJarvis Derlemelerin tanımsız davranışlar karşısında uzaktan doğru kod üretme zorunluluğu yoktur. Bu kodun asla çağrılmadığını bile varsayabilir ve böylece her şeyi bir nop ile değiştirebilir (Derleyicilerin aslında UB karşısında bu varsayımları yaptığını unutmayın). Bu nedenle böyle bir hata raporuna doğru tepki ancak "kapatılabilir, istendiği gibi çalışır" diyebilirim
Grizzly

7
@SchighSchagh Bazen (sadece yüzeyde totolojik bir cevap gibi görünen) terimlerin yeniden ifade edilmesi insanların ihtiyaç duyduğu şeydir. Teknik şartnamelerde yeni olan çoğu insan , çoğu zaman durumdan uzak olan undefined behavioraraçları düşünür something random will happen.
Izkata

27

İki değer aynı olduğu için kurallardan istisna yapmamak için pratik bir neden:

// config.h
#define VALUEA  1

// defaults.h
#define VALUEB  1

// prog.cpp
f(i = VALUEA, i = VALUEB);

Buna izin verildiği durumu düşünün.

Şimdi, birkaç ay sonra, değişme ihtiyacı doğuyor

 #define VALUEB 2

Görünüşte zararsız, değil mi? Ve yine de aniden prog.cpp derlenmeyecekti. Yine de, derlemenin bir değişmez değere bağlı olmaması gerektiğini düşünüyoruz.

Alt satır: kuralın bir istisnası yoktur, çünkü başarılı bir derleme bir sabitin değerine (daha çok türüne) bağlıdır.

DÜZENLE

@ HeartWare , formun sürekli ifadelerine 0 A DIV Bolduğunda B, bazı dillerde izin verilmediğine ve derlemenin başarısız olmasına neden olduğuna dikkat çekti . Bu nedenle bir sabiti değiştirmek başka bir yerde derleme hatalarına neden olabilir. Ki IMHO, talihsiz. Ancak bu tür şeyleri kaçınılmaz olanlarla sınırlamak kesinlikle iyidir.


Tabii, ama örnek yok değişmezleri tamsayı kullanımını. Sizin f(i = VALUEA, i = VALUEB);kesinlikle tanımlanmamış davranış potansiyeline sahiptir. Umarım tanımlayıcıların arkasındaki değerlere karşı gerçekten kodlama yapmazsınız.
Wolf

3
@Wold Ancak derleyici önişlemci makrolarını görmüyor. Ve bu olmasa bile, herhangi bir programlama dilinde bir örnek bulmak zordur, burada bir kaynak kodu 1'den 2'ye kadar int int değişene kadar derlenir. Burada çok iyi açıklamalar görürken, bu sadece kabul edilemez ve açıklanamaz. neden bu kod aynı değerlerle kırılmışsa.
Ingo

Evet, derlemeler makro görmez. Ama bu soru muydu?
Kurt

1
Cevabınız noktayı kaçırıyor, zararlıların cevabını ve OP'nin bu konudaki yorumunu okuyor .
Wolf,

1
Yapabilirdi SomeProcedure(A, B, B DIV (2-A)). Her neyse, eğer dil CONST'ın derleme zamanında tam olarak değerlendirilmesi gerektiğini belirtiyorsa, o zaman elbette, iddiam bu dava için geçerli değildir. Çünkü bir şekilde derleme ve çalışma zamanının ayrımını bulanıklaştırıyor. Yazarsak da fark eder misiniz CONST C = X(2-A); FUNCTION X:INTEGER(CONST Y:INTEGER) = B/Y; ?? Yoksa işlevlere izin verilmiyor mu?
Ingo

12

Karışıklık, sabit bir değeri yerel bir değişkene depolamanın, C'nin üzerinde çalışmak üzere tasarlandığı her mimaride tek bir atomik talimat olmamasıdır. Kodun çalıştığı işlemci bu durumda derleyiciden daha önemlidir. Örneğin, her komutun tam 32 bit sabitini taşıyamadığı ARM'de, bir değişkeni bir int'de saklamak için birden fazla komut gerekir. Bir kerede yalnızca 8 bit saklayabileceğiniz ve 32 bitlik bir kayıtta çalışmanız gereken bu sahte kodla örnek, bir int32'dir:

reg = 0xFF; // first instruction
reg |= 0xFF00; // second
reg |= 0xFF0000; // third
reg |= 0xFF000000; // fourth
i = reg; // last

Derleyici optimize etmek istiyorsa, aynı diziyi iki kez araya sokabileceğini ve i'ye hangi değerin yazılacağını bilmediğinizi hayal edebilirsiniz; ve diyelim ki çok akıllı değil:

reg = 0xFF;
reg |= 0xFF00;
reg |= 0xFF0000;
reg = 0xFF;
reg |= 0xFF000000;
i = reg; // writes 0xFF0000FF == -16776961
reg |= 0xFF00;
reg |= 0xFF0000;
reg |= 0xFF000000;
i = reg; // writes 0xFFFFFFFF == -1

Ancak testlerimde gcc, aynı değerin iki kez kullanıldığını ve bir kez oluşturduğunu ve garip bir şey yapmadığını kabul edecek kadar naziktir. -1, -1 olsun Ama örneğim hala geçerlidir çünkü bir sabitin bile göründüğü kadar açık olmayabileceğini düşünmek önemlidir.


ARM'de derleyicinin sabiti bir tablodan yükleyeceğini düşünüyorum. Açıkladığınız şey daha çok MIPS gibi görünüyor.
ach

1
@AndreyChernyakhovskiy Yep, ancak basitçe olmadığı bir durumda -1(derleyicinin bir yerde saklandığı), ancak daha doğrusu 3^81 mod 2^32, ancak sabit, o zaman derleyici tam olarak burada ne yaptığını yapabilir ve omtimizasyonun bir kolunda, çağrı dizilerini araya ekleyebilirim beklemekten kaçınmak için.
yo

@tohecz, evet, zaten kontrol ettim. Aslında, derleyici bir tablodan her sabiti yüklemek için çok akıllıdır. Her neyse, asla iki sabitin hesaplanması için aynı kaydı kullanmaz. Bu kesinlikle tanımlanmış davranışı 'tanımsızlaştırır'.
ACH

@AndreyChernyakhovskiy Ama muhtemelen "dünyadaki her C ++ derleyici programcısı" değilsiniz. Sadece hesaplamalar için 3 kısa kayıtlı makinelerin bulunduğunu unutmayın.
yo

@tohecz, ve iki ayrı nesnenin f(i = A, j = B)nerede olduğu örneğini düşünün . Bu örnekte UB yoktur. 3 kısa üzerinde olan makine derleyici iki değerlerini karıştırmak için için mazeret ve bu programın semantiğini kıracak çünkü (aynı @ davidf cevabı gösterilmiştir) aynı kayıtta. ijAB
ACH

11

"Yararlı" olmaya çalışan bir derleyicinin tamamen beklenmedik davranışlara neden olacak bir şey yapmasının akla yatkın bir nedeni varsa, davranış genellikle tanımsız olarak belirtilir.

Değişkenlerin birden çok kez yazılması durumunda, yazma işlemlerinin farklı zamanlarda gerçekleşmesini sağlamak için, bazı donanım türleri, çift bağlantı noktalı bellek kullanılarak farklı adreslere aynı anda birden çok "depolama" işleminin gerçekleştirilmesine izin verebilir. Bununla birlikte, bazı çift bağlantı noktalı bellekler, iki mağazanın aynı adrese aynı anda çarptığı senaryoyu, yazılı değerlerin eşleşip eşleşmediğine bakılmaksızın açıkça yasaklar. Böyle bir makine için bir derleyici aynı değişkeni yazmak için iki sıralanmamış denemeyi fark ederse, derlemeyi reddedebilir veya iki yazmanın aynı anda zamanlanamayacağından emin olabilir. Ancak, erişimlerden biri veya her ikisi de bir işaretçi veya başvuru yoluyla yapılıyorsa, derleyici her zaman her iki yazının da aynı depolama konumuna çarpıp çarpmayacağını söyleyemeyebilir. Bu durumda, yazma işlemlerini aynı anda zamanlayabilir ve erişim girişiminde bir donanım tuzağına neden olabilir.

Tabii ki, birisinin böyle bir platformda bir C derleyicisi uygulayabilmesi, bu tür bir davranışın, atomik olarak işlenecek kadar küçük tipteki depoları kullanırken donanım platformlarında tanımlanmaması gerektiğini göstermez. İki farklı değeri sıralanmamış bir şekilde saklamaya çalışmak, bir derleyici farkında değilse tuhaflığa neden olabilir; örneğin, verilen:

uint8_t v;  // Global

void hey(uint8_t *p)
{
  moo(v=5, (*p)=6);
  zoo(v);
  zoo(v);
}

derleyici "moo" çağrısını sıraya koyarsa ve "v" yi değiştirmediğini söyleyebilirse, 5'ten v'ye depolayabilir, sonra 6'dan * p'ye depolayabilir, sonra 5'i "hayvanat bahçesine" geçirebilir ve sonra v içeriğini "hayvanat bahçesine" iletir. "Hayvanat bahçesi" "v" yi değiştirmezse, iki çağrının farklı değerlerden geçirilmesinin hiçbir yolu olmamalıdır, ancak bu yine de kolayca gerçekleşebilir. Öte yandan, her iki mağazanın da aynı değeri yazacağı durumlarda, böyle bir tuhaflık oluşamazdı ve çoğu platformda bir uygulamanın tuhaf bir şey yapması için mantıklı bir neden olmazdı. Ne yazık ki, bazı derleyici yazarları "Standart izin verdiği için" ötesindeki aptalca davranışlar için herhangi bir bahaneye ihtiyaç duymazlar, bu nedenle bu durumlar bile güvenli değildir.


9

Bu durumda çoğu uygulamada sonucun aynı olacağı tesadüfi; değerlendirme sırası hala tanımlanmamıştır. Şunu düşünün f(i = -1, i = -2): burada, sipariş önemlidir. Örneğinizde önemli olmayan tek sebep, her iki değerin de kazasıdır -1.

İfadenin tanımlanmamış bir davranışla belirtildiği göz önüne alındığında f(i = -1, i = -1), yürütmeyi değerlendirip iptal ettiğinizde kötü niyetli olarak uyumlu bir derleyici uygunsuz bir görüntü gösterebilir ve yine de tamamen doğru kabul edilir. Neyse ki, farkında olduğum derleyici yok.


8

Bana işlev argümanı ifadesinin sıralanmasına ilişkin tek kural burada gibi geliyor:

3) Bir işlevi çağırırken (işlevin satır içi olup olmadığı ve açık işlev çağrısı sözdiziminin kullanılıp kullanılmadığı), herhangi bir bağımsız değişken ifadesiyle veya çağrılan işlevi belirten postfix ifadesiyle ilişkili her değer hesaplaması ve yan etki, çağrılan fonksiyonun gövdesindeki her ifade veya ifadenin yürütülmesinden önce sıralanır.

Bu, argüman ifadeleri arasındaki sıralamayı tanımlamaz, bu nedenle bu durumda sonuçlanır:

1) Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki başka bir yan etkiye göre sıralanmamışsa, davranış tanımsızdır.

Uygulamada, çoğu derleyicide alıntıladığınız örnek iyi çalışır ("sabit diskinizi silmek" ve diğer teorik olarak tanımlanmamış davranış sonuçlarının aksine).
Bununla birlikte, atanan iki değer aynı olsa bile, belirli derleyici davranışına bağlı olduğu için bir yükümlülüktür. Ayrıca, açık bir şekilde, farklı değerler atamayı denerseniz, sonuçlar "gerçekten" tanımsız olur:

void f(int l, int r) {
    return l < -1;
}
auto b = f(i = -1, i = -2);
if (b) {
    formatDisk();
}

8

C ++ 17 daha katı değerlendirme kurallarını tanımlar. Özellikle, işlev argümanlarını sıralar (belirtilmemiş sırada olmasına rağmen).

N5659 §4.6:15
Değerlendirme bir ve B belirsiz ya da zaman sekanslanır bir önceki sekanslanır B veya B daha önce sıralanmış olan A , ancak burada belirtilmemiştir. [ Not : Belirsiz olarak sıralanan değerlendirmeler üst üste binemez, ancak önce ikisi de yürütülebilir. - son not ]

N5659 § 8.2.2:5
İlişkilendirilmiş her değer hesaplaması ve yan etki dahil olmak üzere bir parametrenin başlatılması, diğer herhangi bir parametreninkine göre belirsiz bir şekilde sıralanır.

Daha önce UB olabilecek bazı vakalara izin verir:

f(i = -1, i = -1); // value of i is -1
f(i = -1, i = -2); // value of i is either -1 or -2, but not specified which one

2
Bu güncellemeyi c ++ 17 için eklediğiniz için teşekkür ederim , bu yüzden zorunda kalmadım. ;)
Yakk - Adam Nevraumont

Harika, bu cevap için çok teşekkürler. Hafif takip: eğer fbireyin imzası vardı f(int a, int b), C ++ 17 garanti yaptığı a == -1ve b == -2ikinci durumda olduğu gibi adlandırılan olur?
Nicu Stiurca

Evet. Eğer parametrelerimiz varsa ave bsonra i-ondan- a-1'e, daha sonra i-ondan- -2'ye bveya yoluna sıfırlanır. Her iki durumda da a == -1ve ile sonuçlanırız b == -2. En azından ben bu şekilde okudum " Her ilişkili değer hesaplaması ve yan etki de dahil olmak üzere bir parametrenin başlatılması, diğer herhangi bir parametreninkine göre belirsiz bir şekilde sıralanır ".
AlexD

Sanırım sonsuza dek C'de de aynı.
fuz

5

Atama operatörü aşırı yüklenebilir, bu durumda sipariş önemli olabilir:

struct A {
    bool first;
    A () : first (false) {
    }
    const A & operator = (int i) {
        first = !first;
        return * this;
    }
};

void f (A a1, A a2) {
    // ...
}


// ...
A i;
f (i = -1, i = -1);   // the argument evaluated first has ax.first == true

1
Yeterince doğru, ancak soru, diğerlerinin işaret ettiği skaler tipler hakkındaydı , aslında int ailesi, kayan nokta ailesi ve işaretçiler anlamına geliyordu.
Nicu Stiurca

Bu durumda asıl sorun, atama operatörünün durumsal olmasıdır, bu nedenle değişkenin düzenli manipülasyonu bile bu gibi sorunlara eğilimlidir.
AJMansfield

2

Bu sadece "int veya float gibi bir şeyin yanı sıra" skaler nesne "nin ne anlama geleceğinden emin değilim.

"Skaler nesne", "skaler tip nesne" veya sadece "skaler tip değişken" kısaltması olarak yorumlamak. Ardından pointer, enum(sabit) skaler tiptedir.

Bu Skaler Türlerin bir MSDN makalesidir .


Bu biraz "sadece bağlantı yanıtı" gibi okunur. İlgili bitleri bu bağlantıdan bu yanıta kopyalayabilir misiniz (bir blok alıntıda)?
Cole Johnson

1
@ColeJohnson Bu sadece bir link cevap değildir. Bağlantı sadece daha fazla açıklama içindir. Cevabım "pointer", "enum".
Peng Zhang

Ben cevap demedim oldu bir bağlantı yalnızca cevap. "[Bir] gibi okunur" dedim . Yardım bölümünde neden yalnızca bağlantı yanıtları istemediğimizi okumanızı öneririz. Bunun nedeni, Microsoft sitelerinde URL'lerini güncellerse, bu bağlantının kesilmesidir.
Cole Johnson

1

Aslında, derleyicinin iaynı değerle iki kez atandığını kontrol edeceğine bağlı olmamak için bir neden vardır , böylece tek bir atama ile değiştirmek mümkündür. Ya bazı ifadelerimiz varsa?

void g(int a, int b, int c, int n) {
    int i;
    // hey, compiler has to prove Fermat's theorem now!
    f(i = 1, i = (ipow(a, n) + ipow(b, n) == ipow(c, n)));
}

1
Sadece atayın: Fermat teoremini ispat gerek yok 1etmek i. Her iki argüman da atanır 1ve bu "doğru" şeyi yapar veya bağımsız değişkenler farklı değerler atar ve tanımsız davranışı dolayısıyla seçimimize izin verilir.
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.