Bu yapılar neden ön ve artım sonrası tanımsız davranışlar kullanıyor?


814
#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}

12
@ Jarett, hayır, sadece "sıralama noktaları" için bazı işaretçiler gerekiyordu. Çalışırken i = i ++ ile bir parça kod buldum, "Bu i değerini değiştirmez" thougth. Test ettim ve nedenini merak ettim. Çünkü bu ifadeyi kaldırdım ve i ++ ile değiştirdim;
PiX

198
Herkesin DAİMA bunun gibi soruların sorulduğunu varsaymasının ilginç olduğunu düşünüyorum çünkü asker söz konusu yapıyı kullanmak istiyor. İlk varsayımım, PiX'in bunların kötü olduğunu biliyor olmasıydı, ama neden kullandıkları whataver derleyicisindeki gibi davrandıklarını merak ediyor ... Ve evet, unwind ne dedi ... undefined, her şeyi yapabilirdi. .. JCF dahil (Atla ve Yakala Ateş)
Brian Postow

32
Merak ediyorum: Derleyiciler neden "u = u ++ + ++ u;" sonuç tanımsızsa?
OpenGL ES

5
(i++)parantez ne olursa olsun hala 1 olarak değerlendirilir
Drew McGowen

2
Ne i = (i++);yapmak isterse yapsın, kesinlikle yazmanın daha net bir yolu var. İyi tanımlanmış olsa bile bu doğru olur. Davranışını tanımlayan Java'da bile, i = (i++);yine de kötü bir kod. Sadece yazmaki++;
Keith Thompson

Yanıtlar:


566

C, tanımlanmamış davranış kavramına sahiptir, yani bazı dil yapıları sözdizimsel olarak geçerlidir, ancak kod çalıştırıldığında davranışı tahmin edemezsiniz.

Bildiğim kadarıyla, standart açıkça tanımlanmamış davranış kavramının neden var olduğunu söylemiyor . Benim düşünceme göre, dil tasarımcıları anlambilimde bir miktar boşluk olmasını istedikleri için, yani tüm uygulamaların tamsayı taşmasını tam olarak aynı şekilde ele almasını zorunlu kılmak, bu da büyük olasılıkla ciddi performans maliyetleri getirecek, sadece davranıştan ayrıldılar ungerfined, böylece tamsayı taşmasına neden olan kod yazarsanız, her şey olabilir.

Peki, bunu akılda tutarak, neden bu "sorunlar"? Dil, bazı şeylerin tanımlanmamış davranışa yol açtığını açıkça söylüyor . Sorun yok, ilgili "olmazsa olmaz" diye bir şey yok. İlgili değişkenlerden biri bildirildiğinde tanımlanmamış davranış değişirse volatile, bu hiçbir şeyi kanıtlamaz veya değiştirmez. Öyle tanımsız ; davranış hakkında akıl yürütemezsin.

En ilginç görünen örneğiniz,

u = (u++);

tanımlanmamış davranışın bir ders kitabı örneğidir (Wikipedia'nın sıralama noktalarındaki girişine bakın ).


8
@PiX: Bir dizi olası nedenden dolayı işler tanımsız. Bunlar arasında açık bir "doğru sonuç" yoktur, farklı makine mimarileri farklı sonuçları şiddetle tercih eder, mevcut uygulama tutarlı değildir veya standardın kapsamı dışındadır (örneğin, hangi dosya adları geçerli).
Richard

Sadece herkesi karıştırmak için, bu tür bazı örnekler şimdi C11'de iyi tanımlanmıştır, örn i = ++i + 1;.
MM

2
Standardı ve yayınlanan mantığı okurken, UB kavramının neden var olduğu açıktır. Standart hiçbir zaman bir C uygulamasının belirli bir amaca uygun olması için yapması gereken her şeyi tam olarak tanımlamak için tasarlanmamıştır (bkz. "Bir Program" kuralının tartışılması), bunun yerine uygulayıcıların yararlı kalite uygulamaları üretme kararına ve arzusuna dayanır. Düşük seviyeli sistem programlaması için uygun bir kalite uygulamasının, üst düzey sayı kırılmasında gerekmeyecek eylemlerin davranışını tanımlaması gerekecektir. Uygulamalar. Standardı karmaşıklaştırmaya çalışmak yerine ...
supercat

3
... hangi köşe vakalarının tanımlandığı veya tanımlanmadığı konusunda aşırı ayrıntılara girerek, Standardın yazarları, destekleyicilerin desteklemeleri beklenen program türleri tarafından ne tür davranışlara ihtiyaç duyulacağını yargılamak için daha iyi olması gerektiğini fark ettiler. . Hiper-modernist derleyiciler, bazı eylemlerin yapılmasının UB'nin hiçbir kalite programının bunlara ihtiyaç duymaması gerektiğini ima ettiğini iddia ediyorlardı, ancak Standart ve mantık böyle bir niyetle tutarsız.
supercat

1
@jrh: Bu cevabı hiper-modernist felsefenin ne kadar elden geçtiğini fark etmeden yazdım. Beni rahatsız eden şey, "Bu davranışı resmen tanımamız gerekmiyor, çünkü ihtiyaç duyulan platformlar yine de onu destekleyebilir" den "Bu işlevi, hiçbir zaman tanınmadığı ve dolayısıyla herhangi bir kod olmadığı için kullanılabilir bir değiştirme sağlamadan kaldırabiliriz. buna ihtiyaç vardı ". Birçok davranış , her şekilde daha iyi olan , ancak meşruiyetlerini kabul etmeyi gerektiren yedekler lehine uzun zaman önce onaylanmamış olmalıdır .
supercat

78

Sadece kod satırınızı derleyin ve sökün, eğer ne elde edeceğinizi tam olarak nasıl bildiğinizi öğrenmeye eğilimliyseniz.

Bu benim makinemde ne olduğunu ve neler olduğunu düşünüyorum:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(Ben ... varsayalım 0x00000014 talimat derleyici optimizasyonu bir tür olduğunu?)


makine kodunu nasıl alabilirim? Dev C ++ kullanıyorum ve derleyici ayarlarında 'Kod Üretimi' seçeneği ile oynadım, ancak ekstra dosya çıktısı veya herhangi bir konsol çıktısı
gitmiyorum

5
@ronnieaka gcc evil.c -c -o evil.binve gdb evil.bindisassemble evil, ya da bunların Windows eşdeğerleri ne olursa olsun :)
badp

21
Bu cevap gerçekten sorusunu ele almıyor Why are these constructs undefined behavior?.
Shafik Yaghmour

9
Bir kenara, gcc -S evil.cburada gerekli olan tek şey montajla (ile ) derlemek daha kolay olacaktır . Montajı ve sökülmesi sadece dolambaçlı bir yöntemdir.
Kat

50
Kayıt için, herhangi bir nedenden dolayı belirli bir yapının ne yaptığını merak ediyorsanız - ve özellikle de tanımlanmamış davranış olabileceğinden şüphe duyuyorsanız - "sadece derleyicinizle deneyin ve görün" potansiyel olarak oldukça tehlikeli. En iyi ihtimalle, derleyicinizin bu sürümü altında, bu koşullar altında ne yaptığını öğreneceksiniz . Sen edecek değil , daha büyük pilli eden herhangi birşey yapmak eğer çok öğrenirler. Genel olarak, "sadece derleyicinizle deneyin" yalnızca derleyicinizle çalışan taşınabilir olmayan programlara yol açar.
Steve Summit

64

Bence C99 standardının ilgili kısımları 6.5 İfadeler, §2

Önceki ve sonraki dizi noktası arasında, bir nesnenin depolanan değeri bir ifadenin değerlendirilmesi ile en fazla bir kez değiştirilmelidir. Ayrıca, önceki değer sadece depolanacak değerin belirlenmesi için okunmalıdır.

ve 6.5.16 Atama operatörleri, §4:

İşlenenlerin değerlendirme sırası belirtilmemiştir. Bir atama operatörünün sonucunu değiştirmek veya bir sonraki sıra noktasından sonra ona erişmek için girişimde bulunulursa, davranış tanımsızdır.


2
Yukarıda 'i = i = 5; "Tanımsız Davranış olur mu?
supercat

1
bildiğim kadarıyla @supercat i=i=5de undefined davranışı
dhein

2
@Zaibis: Çoğu yerde kullanmak istediğim mantık kuralı, bir mutli-işlemci platformunun A=B=5;"Write-lock A; Write-Lock B; Store 5 to A; store 5 to B; Unlock B ; Unock A; "ve C=A+B;" Okuma kilidi A; Okuma kilidi B; Hesaplama A + B; A ve B kilidini aç; Yazma kilidi C; Sakla; C kilidini aç; "gibi bir ifade. Bu, bir iş parçacığı A=B=5;başka bir iş parçacığı tarafından yapılırsa C=A+B;, ikinci iş parçacığının her iki yazıyı da gerçekleştiğini veya hiçbirinin görmediğini garanti eder . Potansiyel olarak yararlı bir garanti. Bir iş parçacığı yaptı I=I=5;, ancak ...
Supercat

1
... ve derleyici her iki yazının da aynı konumda olduğunu fark etmedi (eğer bir veya her iki değer de işaretçileri içeriyorsa, belirlenmesi zor olabilir), oluşturulan kod kilitlenebilir. Herhangi bir gerçek dünya uygulamasının normal davranışlarının bir parçası olarak bu tür bir kilitleme uyguladığını düşünmüyorum, ancak standart altında izin verilebilir ve donanım bu tür davranışları ucuza uygulayabilirse yararlı olabilir. Bugünün donanımında, bu tür davranışlar varsayılan olarak uygulanamayacak kadar pahalı olurdu, ancak bu her zaman böyle olacağı anlamına gelmez.
supercat

1
@ supercat, ancak c99'un sıra noktası erişim kuralı onu tanımsız davranış olarak bildirmek için yeterli olmaz mı? Yani donanımın teknik olarak ne yapabileceği önemli değil mi?
dhein

55

Burada C standardından alıntılanan cevapların çoğu, bu yapıların davranışlarının tanımsız olduğunu vurgulamaktadır. Anlamak için bu yapıları davranışı tanımsız neden , en C11 standardının ışığında ilk bu terimleri anlayalım:

Sıralı: (5.1.2.3)

Herhangi iki değerlendirmeler göz önüne alındığında Ave Beğer Adaha önce dizilenmekte Bsonra yürütme, Ayürütülmesini önce edecektir B.

unsequenced:

Eğer Aönce veya sonra dizilemezse B, Ave Bsıralanmamıştır.

Değerlendirmeler iki şeyden biri olabilir:

  • bir ifadenin sonucunu hesaplayan değer hesaplamaları ; ve
  • nesnelerin modifikasyonları olan yan etkiler .

Dizi Noktası:

İfadelerin değerlendirilmesi arasında bir dizi noktasının bulunması Ave Bgerektirir ki, her değeri hesaplaması ve yan etki ile bağlantılı Aher önce sekanslanır değeri hesaplama ve yan etki ile bağlantılı B.

Şimdi soruya geliyorum,

int i = 1;
i = i++;

standart diyor ki:

6.5 İfadeler:

Skalar nesne üzerinde bir yan etki için unsequenced göre ise , ya aynı skalar nesne üzerinde farklı bir yan etki ya da aynı sayısal nesnesinin değerini kullanarak bir değer hesaplama, davranış tanımlanmamış . [...]

Bu nedenle, yukarıdaki ifade iUB'yi başlatır çünkü aynı nesne üzerindeki iki yan etki birbirine göre sıralanmamıştır. Bu, atama ile yan etkinin, yan etkiden iönce mi sonra mı yapılacağı sıralanmamıştır ++.
Atamanın, artıştan önce veya sonra gerçekleşmesine bağlı olarak, farklı sonuçlar üretilecektir ve bu, tanımlanmamış davranış durumlarından biridir .

Adlandırmak Sağlar iolmak atamanın sol ucundaki ilve (ifadesindeki atamanın sağda i++) olmak ir, daha sonra ifadesi gibi olmak

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Postfix ++operatörü ile ilgili önemli bir nokta şudur:

sırf ++değişken artım geç olur anlamına gelmez sonra gelir . Artış derleyici orijinal değerin kullanılmasını sağladığı sürece derleyicinin beğendiği kadar erken gerçekleşebilir .

Bu, ifadenin il = ir++şu şekilde değerlendirilebileceği anlamına gelir:

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

veya

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

iki farklı sonuç 1ile sonuçlanır ve 2atama ile yan etkilerin sırasına bağlıdır ++ve dolayısıyla UB'yi başlatır.


52

Hem çağırır çünkü davranış gerçekten açıklanamayan belirtilmemiş davranış ve tanımsız davranış bu kod hakkında genel bir tahminde yapamaz öyleyse, okursanız rağmen, Olve Maudal en gibi çalışmalarını Derin C ve Tanımlanmamış ve Tanımsız bazen iyi olmasını sağlayabilen belirli bir derleyici ve çevre ile çok özel durumlarda tahmin eder, ancak lütfen üretimin yakınında hiçbir yerde yapmayın.

Bu nedenle , c99 standart bölüm paragraf 3 taslağında belirtilmemiş davranışa geçiliyor ( benimki vurgu ):6.5

İşleçlerin ve işlenenlerin gruplandırılması sözdizimi ile belirtilir. 74) Daha sonra belirtildiği durumlar dışında (işlev çağrısı (), &&, ||,?: Ve virgül işleçleri için), alt ifadelerin değerlendirme sırası ve hangi yan etkilerin meydana geldiği belirtilmemiş.

Yani böyle bir çizgimiz olduğunda:

i = i++ + ++i;

biz mi bilmiyorum i++ya ++iilk değerlendirilecektir. Bu temel olarak derleyiciye optimizasyon için daha iyi seçenekler sunmaktır .

Ayrıca sahip tanımsız davranış programı değişkenleri değiştirerek (çünkü burada da i, uaralarında birden fazla kez, vb ..) dizi noktaları . Taslak standart bölüm 6.5paragraf 2'den ( vurgu mayını ):

Önceki ve sonraki dizi noktası arasında, bir nesnenin depolanan değeri bir ifadenin değerlendirilmesi ile en fazla bir kez değiştirilmelidir . Ayrıca, önceki değer sadece depolanacak değerin belirlenmesi için okunmalıdır .

aşağıdaki kod örneklerine tanımsız olarak atıfta bulunur:

i = ++i + 1;
a[i++] = i; 

Tüm bu örneklerde kod, aynı sıra noktasında bir nesneyi bir kereden fazla değiştirmeye çalışıyor ;ve bu, bu durumların her birinde sona eriyor :

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

Belirtilmemiş davranış , taslak c99 standardında bölüm 3.4.4olarak şu şekilde tanımlanır :

belirtilmemiş bir değerin veya bu Uluslararası Standardın iki veya daha fazla olasılık sağladığı ve herhangi bir durumda seçilen başka bir şart getirmediği başka bir davranışın kullanılması

ve tanımlanmamış davranışlar bölümünde şu şekilde tanımlanır 3.4.3:

taşınabilir olmayan veya hatalı bir program yapısının veya bu Uluslararası Standardın hiçbir gereklilik getirmediği hatalı verilerin kullanılması üzerine davranış

ve şunu not eder:

Olası tanımlanamayan davranışlar durumu öngörülemeyen sonuçlarla tamamen görmezden gelmekten, çeviri veya program yürütme sırasında ortamın karakteristiği olan (tanı mesajı verilmiş veya verilmeden) belgelenmiş bir şekilde davranmaya, bir çeviri veya yürütmeyi sonlandırmaya (düzenleme ile) kadar uzanır. bir teşhis mesajı).


33

Bunu cevaplamanın bir başka yolu, sıra noktalarının ve tanımsız davranışların gizli ayrıntılarına saplanmaktan ziyade, ne anlama geldiklerini sormaktır. Programcı ne yapmaya çalışıyordu?

Sorulan ilk parça, i = i++ + ++ikitabımda oldukça açık. Hiç kimse bunu gerçek bir programa yazmazdı, ne yaptığı belli değil, birisinin kodlamaya çalıştığı akla yatkın bir algoritma, bu özel işlem dizisine yol açacaktı. Ve sizin ve benim için ne yapması gerektiği belli olmadığından, derleyicinin ne yapması gerektiğini de anlayamaması kitabımda iyi.

İkinci parçayı i = i++anlamak biraz daha kolaydır. Birisi açıkça i'yi artırmaya ve sonucu i'ye atamaya çalışıyor. Ancak bunu C'de yapmanın birkaç yolu vardır. İ'ye 1 eklemenin ve sonucu i'ye atamanın en temel yolu neredeyse tüm programlama dillerinde aynıdır:

i = i + 1

C, elbette, kullanışlı bir kısayola sahip:

i++

Bu, "i'ye 1 ekleyin ve sonucu tekrar i'ye atayın" anlamına gelir. Yani, ikisinden bir hodgepodge inşa edersek,

i = i++

gerçekten dediğimiz şey "i'ye 1 ekleyin ve sonucu i'ye geri atayın ve sonucu tekrar i'ye atayın" dır. Kafamız karıştı, bu yüzden derleyicinin kafası karışırsa beni fazla rahatsız etmez.

Gerçekçi olarak, bu çılgın ifadelerin yazıldığı tek zaman, insanların bunları ++'ın nasıl çalışması gerektiği konusunda yapay örnekler olarak kullandıkları zamandır. Ve elbette ++ 'ın nasıl çalıştığını anlamak önemlidir. Ancak ++ kullanmak için pratik bir kural şudur: "++ kullanarak bir ifadenin ne anlama geldiği belli değilse yazmayın."

Comp.lang.c'de bunun gibi ifadeleri ve neden tanımlanmadığını tartışmak için sayısız saat harcıyorduk . Nedenini açıklamaya çalışan uzun cevaplarımdan ikisi web'de arşivleniyor:

Ayrıca bkz 3.8 sorgulamaya ve içinde kalan soruları bölümünün 3 arasında C SSS listesinde .


1
Tanımsız Davranış bakımından Oldukça kötü yakaladım onu ederken olmasıdır kullanılan güvenli olması için derleyicilerinin% 99.9 kullanımı üzerine *p=(*q)++;anlamında if (p!=q) *p=(*q)++; else *p= __ARBITRARY_VALUE;bu artık bir durumdur. Hiper-modern C, son formülasyon gibi bir şey yazmayı gerektirir (ancak kodun ne olduğunu umursamadığını gösteren standart bir yol yoktur *p), öncekiyle sağlamak için kullanılan verimlilik derleyicilerinin seviyesine ulaşmak için ( elsemadde, izin vermek için gereklidir) derleyici, ifbazı yeni derleyicilerin gerektireceği optimizasyonu sağlar ).
supercat

@supercat Şimdi bu tür bir optimizasyonu gerçekleştirmek için "akıllı" herhangi bir derleyici de ifadelere bakmak için yeterince akıllı olması gerektiğine inanıyorum assert, böylece programcı söz konusu satırdan önce basit olabilir assert(p != q). (Elbette, bu kursu almak, <assert.h>hata ayıklamayan sürümlerde iddiaları açıkça silmemek için yeniden yazmayı gerektirir , daha ziyade, bunları __builtin_assert_disabled()derleyicinin görebileceği ve daha sonra kod yayınlayamayacağı gibi bir şeye dönüştürün .)
Steve Summit

25

Genellikle bu soru, kodla ilgili soruların bir kopyası olarak

printf("%d %d\n", i, i++);

veya

printf("%d %d\n", ++i, i++);

veya benzer varyantlar.

Bu, daha önce de belirtildiği gibi tanımlanmamış bir davranış olsa da , aşağıdakiler printf()gibi bir ifadeyle karşılaştırılırken küçük farklılıklar vardır :

x = i++ + i++;

Aşağıdaki ifadede:

printf("%d %d\n", ++i, i++);

Değerlendirme sırasını argümanların printf()olduğunu belirtilmemiş . Yani, ifadeler i++ve ++iherhangi bir sırayla değerlendirilebilir. C11 standardı bununla ilgili bazı tanımlara sahiptir:

Ek J, belirtilmemiş davranışlar

Bir işlev çağrısında işlev belirleyicisinin, bağımsız değişkenlerin ve bağımsız değişkenler içindeki alt ifadelerin değerlendirilme sırası (6.5.2.2).

3.4.4, belirtilmemiş davranış

Belirtilmemiş bir değerin veya bu Uluslararası Standardın iki veya daha fazla olasılık sağladığı ve herhangi bir durumda seçilen başka bir şart getirmediği başka bir davranışın kullanılması.

ÖRNEK Belirtilmemiş davranışa bir örnek, bir işleve yönelik argümanların değerlendirilme sırasıdır.

Belirtilmemiş davranış kendisi bir sorun değildir. Bu örneği düşünün:

printf("%d %d\n", ++x, y++);

Bu da belirtilmemiş bir davranışa sahiptir, çünkü değerlendirme sırası ++xve y++belirtilmemiş. Ancak bu yasal ve geçerli bir ifadedir. Orada hiçbir bu açıklamada tanımsız davranış. Çünkü değişiklikler ( ++xve y++) farklı nesnelerde yapılır .

Aşağıdaki ifadeyi oluşturan nedir

printf("%d %d\n", ++i, i++);

olarak tanımlanmamış bir davranış bu iki ifade değiştirme gerçektir aynı nesne ibir müdahale etmeden dizisi noktası .


Başka ayrıntı olduğunu virgül printf () çağrısında yer alan bir olan ayırıcı değil, virgül operatörü .

Bu önemli bir ayrımdır, çünkü virgül operatörü , işlenenlerinin değerlendirilmesi arasında aşağıdakileri yasal yapan bir dizi noktası ekler:

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

Virgül operatörü, işlenenlerini soldan sağa değerlendirir ve yalnızca son işlenenin değerini verir. Bu yüzden de j = (++i, i++);, ++iartışlarla iiçin 6ve i++verimler eski değerini i( 6atanır) j. Sonra artış sonrası iolur 7.

Yani eğer virgül işlev çağrısında ardından virgül operatörü olmak vardı

printf("%d %d\n", ++i, i++);

sorun olmayacak. Ancak tanımlanmamış davranışı başlatır, çünkü buradaki virgül bir ayırıcıdır .


Tanımlanamayan davranışa yeni başlayanlar için , her C Programcısının Tanımlanmamış Davranış Hakkında Bilmesi Gerekenler'i okumak ve C'deki tanımlanmamış davranışların diğer birçok varyantını anlamak için faydalanacaktır.

Bu gönderi: Tanımlanmamış, belirtilmemiş ve uygulama tanımlı davranışlar da önemlidir.


Bu dizinin int a = 10, b = 20, c = 30; printf("a=%d b=%d c=%d\n", (a = a + b + c), (b = b + b), (c = c + c));kararlı davranış verdiği görülüyor (gcc v7.3.0'da sağdan sola argüman değerlendirmesi; sonuç "a = 110 b = 40 c = 60"). Ödevler 'tam ifadeler' olarak kabul edildiğinden ve bu nedenle bir dizi noktası getirdiğinden mi? Bu, soldan sağa argüman / ifade değerlendirmesi ile sonuçlanmamalı mı? Yoksa sadece tanımlanmamış davranışların tezahürü mü?
kavadias

@kavadias Bu printf ifadesi, yukarıda açıklanan aynı nedenden ötürü tanımlanmamış davranış içerir. Sırasıyla 3. ve 4. bağımsız değişkenlerde yazıyorsunuz bve c2. bağımsız değişkente okuyorsunuz. Ancak bu ifadeler arasında sıra yoktur (2., 3. ve 4. argümanlar). gcc / clang, -Wsequence-pointbunları bulmanıza da yardımcı olabilecek bir seçeneğe sahiptir.
PP

23

Herhangi bir derleyici ve işlemcinin bunu yapması pek olası olmasa da, C standardı altında derleyicinin "i ++" dizisini sıralamasıyla yasal olması gerekir:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Herhangi bir işlemcinin böyle bir şeyin verimli bir şekilde yapılmasına izin vermek için donanımı desteklediğini düşünmese de, böyle bir davranışın çok iş parçacıklı kodu daha kolay hale getireceği durumları kolayca hayal edebiliyorum (örneğin, iki iş parçacığı yukarıdaki işlemleri yapmaya çalışırsa garanti eder) eşzamanlı olarak, iikiye katlanır) ve gelecekteki bazı işlemcilerin böyle bir özellik sağlayabileceği tamamen düşünülemez.

Derleyici i++yukarıda belirtildiği gibi yazacaksa (standart altında yasal) ve genel ifadenin (ayrıca yasal olarak) değerlendirilmesi sırasında yukarıdaki talimatları kesişecekse ve diğer talimatlardan birinin gerçekleştiğini fark etmediyse erişmek iiçin, derleyicinin kilitlenecek bir dizi talimat üretmesi mümkün (ve yasal) olacaktır. Emin olmak için bir derleyici neredeyse kesinlikle aynı değişken durumda sorunu tespit edecek iher iki yerde de kullanılır, ancak rutin iki işaretçiler başvurular kabul ederse pve qve kullanımları (*p)ve (*q)yukarıdaki ifadenin (yerine kullanarakiiki kez) derleyiciden aynı nesnenin adresi hem pve hem de için geçilirse oluşan kilitlenmeyi tanıması veya bunlardan kaçınması gerekmez q.


16

İken sözdizimi ifadelerin gibi a = a++veya a++ + a++yasal, davranış bu yapıların olduğu tanımsız bir nedeni olmalıdır C standardında itaat edilmez. C99 6.5p2 :

  1. Önceki ve sonraki dizi noktası arasında, bir nesnenin depolanan değeri bir ifadenin değerlendirilmesi ile en fazla bir kez değiştirilmelidir. [72] Ayrıca, önceki değer sadece depolanacak değerin belirlenmesi için okunmalıdır [73]

İle dipnot 73 bundan başka açıklık

  1. Bu paragraf aşağıdaki gibi tanımlanmamış ifade ifadeleri verir

    i = ++i + 1;
    a[i++] = i;

    izin verirken

    i = i + 1;
    a[i] = i;

Çeşitli sekans noktaları C11 (ve C99 ) Ek C'de listelenmiştir :

  1. 5.1.2.3'te açıklanan dizi noktaları şunlardır:

    • Fonksiyon göstergesinin ve bir fonksiyon çağrısındaki gerçek argümanların ve gerçek çağrının değerlendirmeleri arasında. (6.5.2.2).
    • Aşağıdaki operatörlerin birinci ve ikinci işlenenlerinin değerlendirmeleri arasında: mantıksal AND && (6.5.13); mantıksal VEYA || (6.5.14); virgül, (6.5.17).
    • Koşullu ilk işlenen değerlendirmeleri arasında? : operatör ve ikinci ve üçüncü işlenenden hangisi değerlendirilir (6.5.15).
    • Tam bir bildirgenin sonu: bildiriciler (6.7.6);
    • Tam ifadenin değerlendirilmesi ile değerlendirilecek bir sonraki tam ifade arasında. Aşağıdakiler tam ifadelerdir: bileşik bir değişmezin (6.7.9) bir parçası olmayan bir başlatıcı; bir ifade ifadesindeki ifade (6.8.3); bir seçim ifadesinin (if veya switch) kontrol ifadesi (6.8.4); bir while veya do ifadesinin kontrol ifadesi (6.8.5); for ifadesinin (6.8.5.3) (isteğe bağlı) ifadelerinin her biri; bir return ifadesindeki (isteğe bağlı) ifade (6.8.6.4).
    • Bir kütüphane işlevi geri dönmeden hemen önce (7.1.4).
    • Her biçimlendirilmiş giriş / çıkış işlevi dönüşüm belirteciyle (7.21.6, 7.29.2) ilişkili eylemlerden sonra.
    • Bir karşılaştırma fonksiyonuna yapılan her çağrıdan hemen önce ve hemen sonra ve ayrıca bir karşılaştırma fonksiyonuna yapılan herhangi bir çağrı ile o çağrıya argüman olarak iletilen nesnelerin hareketi arasında (7.22.5).

C11'de aynı paragrafın ifadeleri :

  1. Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki farklı bir yan etkiye veya aynı skaler nesnenin değerini kullanan bir değer hesaplamasına göre sıralanmamışsa, davranış tanımsızdır. Bir ifadenin alt ifadelerinin birden fazla izin verilebilir sırası varsa, bu sıralardan herhangi birinde sıralanmamış bir yan etki oluşursa davranış tanımsızdır.84)

Bir programda bu tür hataları örneğin GCC'nin son sürümü olan -Wallve kullanarak kullanarak tespit edebilirsiniz -Werrorve daha sonra GCC programınızı derlemeyi reddeder. Aşağıdaki gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005 çıktısıdır:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function main’:
plusplus.c:6:6: error: operation on i may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on i may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on i may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on u may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on u may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on u may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on v may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on v may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

Önemli bir parçası bilmektir bir dizi noktanın ne - ve ne bir dizi noktası ve hangi değildir . Örneğin, virgül operatörü bir sıralama noktasıdır, dolayısıyla

j = (i ++, ++ i);

iyi tanımlanmıştır ve ieski değeri vererek birer birer artar , bu değeri atar; sonra virgül operatörünün yan etkilerini düzeltin; ve sonra ibirer birer artırılır ve elde edilen değer ifadenin değeri haline gelir - yani bu sadece yazmak j = (i += 2)için "akıllı" bir yol olan yazmanın sadece bir yolu

i += 2;
j = i;

Ancak, ,işlev bağımsız değişkeni listeleri virgül işleci değildir ve farklı bağımsız değişkenlerin değerlendirmeleri arasında bir sıralama noktası yoktur; bunun yerine değerlendirmeleri birbirlerine göre değerlendirilmemiştir; böylece fonksiyon çağrısı

int i = 0;
printf("%d %d\n", i++, ++i, i);

tanımlanmamış bir davranışa sahiptir, çünkü fonksiyon argümanlarının değerlendirilmesinde i++ve ++iiçinde argümanların değerlendirilmesinde bir dizi noktası yoktur ve bu inedenle değeri, hem önceki hem de sonraki dizi noktası arasında iki i++ve iki kez değiştirilir ++i.


14

C standardı, bir değişkenin iki dizi noktası arasında en fazla bir kez atanması gerektiğini belirtir. Örneğin bir noktalı virgül bir sekans noktasıdır.
Yani formun her ifadesi:

i = i++;
i = i++ + ++i;

ve böylece bu kuralı ihlal ediyorlar. Standart ayrıca davranışın tanımsız olduğunu ve belirtilmediğini söylüyor. Bazı derleyiciler bunları algılar ve sonuç verir, ancak bu standart başına değildir.

Bununla birlikte, iki dizi noktası arasında iki farklı değişken artırılabilir.

while(*src++ = *dst++);

Yukarıdakiler, dizeleri kopyalarken / analiz ederken yaygın bir kodlama uygulamasıdır.


Tabii ki bir ifadedeki farklı değişkenler için geçerli değildir. Olsaydı toplam tasarım hatası olurdu! İkinci örnekte ihtiyacınız olan her şey, hem ifade bitişi ile bir sonraki başlangıç ​​arasında artırılmalıdır hem de bu, tüm bunların merkezindeki sıra noktaları kavramı nedeniyle garanti edilir.
underscore_d

11

Gelen /programming/29505280/incrementing-array-index-in-c birisi gibi bir deyimi hakkında sorular:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

ki bu 7 ... OP'nin yazdırmasını bekledi 6.

++iArtışlarla hesaplamaların kalanı önce tüm tam garanti edilmez. Aslında, farklı derleyiciler burada farklı sonuçlar alacaktır. Sağladığınız örnekte, ilk 2 ++isonra değerleri, idam k[]son sonra, okunmuştur ++isonra k[].

num = k[i+1]+k[i+2] + k[i+3];
i += 3

Modern derleyiciler bunu çok iyi optimize edecektir. Aslında, muhtemelen yazdığınız koddan daha iyi (umduğunuz şekilde çalıştığını varsayarak).


5

Hesaplama bu tür ne olduğu hakkında iyi bir açıklama belge sağlanan n1188 gelen ISO W14 sitesinde .

Fikirleri açıklıyorum.

Bu durumda geçerli olan ISO 9899 standardının ana kuralı 6.5p2'dir.

Önceki ve sonraki dizi noktası arasında, bir nesnenin depolanan değeri bir ifadenin değerlendirilmesi ile en fazla bir kez değiştirilmelidir. Ayrıca, önceki değer sadece depolanacak değerin belirlenmesi için okunmalıdır.

Bir ifadedeki dizi noktaları i=i++önceki i=ve sonraki gibidir i++.

Yukarıda alıntıladığım makalede, her kutuda birbirini takip eden 2 sıra noktası arasındaki talimatları içeren küçük kutulardan oluşan programı anlayabileceğiniz açıklanmaktadır. Dizi noktaları, standardın ek C'sinde tanımlanır, i=i++tam ifade sınırlayan 2 dizi noktası olması durumunda . Böyle bir ifade, expression-statementdilbilgisinin Backus-Naur formundaki bir girişle sözdizimsel olarak eşdeğerdir (Standardın Ek A'sında bir dilbilgisi sağlanır).

Yani bir kutu içindeki talimatların sırasının net bir sırası yoktur.

i=i++

olarak yorumlanabilir

tmp = i
i=i+1
i = tmp

veya gibi

tmp = i
i = tmp
i=i+1

çünkü kodu yorumlamak için bu formların her ikisi i=i++de geçerlidir ve her ikisi de farklı yanıtlar ürettiğinden, davranış tanımsızdır.

Bu nedenle, programı oluşturan her kutunun başında ve sonunda bir sıra noktası görülebilir [kutular C'deki atomik birimlerdir] ve bir kutunun içinde talimatların sırası her durumda tanımlanmamıştır. Bu sırayı değiştirmek bazen sonucu değiştirebilir.

DÜZENLE:

Bu tür belirsizlikleri açıklamak için diğer iyi bir kaynak, c-sss sitesinden ( kitap olarak da yayınlanmaktadır ), yani burada ve burada ve burada yapılan girişlerdir .


Bu cevap mevcut cevaplara nasıl yeni eklendi? Ayrıca, bu cevabai=i++ çok benziyor .
17'de

@haccks Diğer cevapları okumadım. Söz konusu belgeden ISO 9899 resmi sitesinden öğrendiklerimi kendi dilimde açıklamak istedim open-std.org/jtc1/sc22/wg14/www/docs/n1188.pdf
alinsoar

5

Sorunuz muhtemelen "Bu yapılar neden C'de tanımsız davranışlar?" Sorunuz muhtemelen, "Neden bu kod (kullanarak ++) bana beklediğim değeri vermedi ?" Ve birisi sorunuzu kopya olarak işaretledi ve sizi buraya gönderdi.

Bu yanıt şu soruyu cevaplamaya çalışır: kodunuz size beklediğiniz cevabı neden vermedi ve beklendiği gibi çalışmayan ifadeleri tanımayı (ve bunlardan kaçınmayı) nasıl öğrenebilirsiniz.

Şimdiye kadar C'lerin ++ve --operatörlerin temel tanımını ve önek formunun ++xpostfix formundan nasıl farklı olduğunu duyduğunuzu varsayıyorum x++. Ancak bu operatörleri düşünmek zordur, bu yüzden anladığınızdan emin olmak için belki de küçük bir test programı yazdınız.

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

Ama, senin için sürpriz bu program yoktu değil anlamanıza yardımcı - belki düşündüren bazı garip, beklenmedik, açıklanamaz çıktı baskılı ++bunu yaptığını düşündüm ne değildir hiç tamamen farklı bir şey, yok.

Ya da belki de anlaşılması zor bir ifadeye

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

Belki birisi size bu kodu bir bulmaca olarak verdi. Bu kod, özellikle çalıştırırsanız da mantıklı değildir - ve iki farklı derleyici altında derleyip çalıştırırsanız, iki farklı cevap alırsınız! Bunun nesi var? Hangi cevap doğrudur? (Ve cevap şudur ki ikisi de değildir ya da hiçbiri değildir.)

Şimdiye kadar duyduğunuz gibi, tüm bu ifadeler tanımsızdır , yani C dili ne yapacakları konusunda hiçbir garanti vermez. Bu garip ve şaşırtıcı bir sonuç, çünkü yazabileceğiniz herhangi bir programın derlendiği ve çalıştırıldığı sürece benzersiz, iyi tanımlanmış bir çıktı üreteceğini düşündünüz. Ancak tanımlanmamış davranış durumunda, durum böyle değildir.

Bir ifadeyi tanımsız yapan nedir? İfadeler söz konusu mu ++ve --her zaman tanımsız mı? Elbette hayır: bunlar kullanışlı operatörlerdir ve bunları doğru kullanırsanız, mükemmel bir şekilde tanımlanmışlardır.

İfadeleri tanımsız kılan şeylerden bahsediyoruz, bir kerede çok fazla şey olduğunda, hangi siparişlerin içinde olacağından emin olmadığımızda, ancak sipariş sonuç için önemli olduğunda.

Bu cevapta kullandığım iki örneğe geri dönelim. Yazdığımda

printf("%d %d %d\n", x, ++x, x++);

soru, çağırmadan önce printf, derleyici xilk, ya x++da belki de değerini hesaplıyor ++xmu? Ama çıkıyor bilmiyoruz . C'de bir işleve ilişkin argümanların soldan sağa veya sağdan sola veya başka bir sırada değerlendirileceğini söyleyen bir kural yoktur. Biz derleyici yapacağız olmadığını söyleyemeyiz Yani x, sonra ++x, sonra x++, ya x++sonra ++xsonra xveya başka düzen. Ancak sipariş açıkça önemlidir, çünkü derleyicinin kullandığı siparişe bağlı olarak, tarafından yazdırılan farklı sonuçlar açıkça alınır printf.

Bu çılgın ifade ne olacak?

x = x++ + ++x;

Bu ifadedeki sorun, x'in değerini değiştirmek için üç farklı deneme içermesidir: (1) x++bölüm, 1'e x eklemeye, yeni değeri depolamaya xve eski değerini döndürmeye çalışır x; (2) ++xparça x'e 1 eklemeye, yeni değeri depolamaya ve yeni değerini xdöndürmeye çalışır x; ve (3) x =bölüm diğer ikisinin toplamını x'e geri vermeye çalışır. Bu üç denemeden hangisi "kazanacak"? Üç değerden hangisine gerçekten atanacak x? Yine ve belki de şaşırtıcı bir şekilde, C'de bize söyleyecek bir kural yok.

Önceliğin veya ilişkilendirilebilirliğin veya soldan sağa değerlendirmenin, işlerin hangi düzende gerçekleştiğini söylediğini, ancak gerçekleşmediğini hayal edebilirsiniz. Bana inanmayabilirsiniz, ama lütfen benim sözümü kabul edin ve tekrar söyleyeceğim: öncelik ve ilişkilendirilebilirlik, C'deki bir ifadenin değerlendirme sırasının her yönünü belirlemez. Özellikle, bir ifadede birden fazla varsa biz böyle bir şey için yeni bir değer atamak için denemek farklı noktalar x, öncelik ve etkinlikleri yapmak değil önce olursa bu girişimlerin hangi bize, ya da son, ya da bir şey.


Yani tüm bu arka plan ve tanıtım ile, tüm programlarınızın iyi tanımlandığından emin olmak istiyorsanız, hangi ifadeleri yazabilirsiniz ve hangilerini yazamazsınız?

Bu ifadelerin hepsi iyi:

y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;

Bu ifadelerin tümü tanımsızdır:

x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);

Son soru, hangi ifadelerin iyi tanımlandığını ve hangi ifadelerin tanımsız olduğunu nasıl anlayabilirsiniz?

Daha önce de söylediğim gibi, tanımsız ifadeler aynı anda çok fazla şeyin olduğu, hangi siparişlerin içinde gerçekleştiğinden ve siparişin önemli olduğundan emin olamadığınız ifadelerdir:

  1. İki veya daha fazla yerde değiştirilen (atanan) bir değişken varsa, önce hangi değişikliğin gerçekleştiğini nasıl bilebilirsiniz?
  2. Bir yerde değiştirilen ve değerini başka bir yerde kullanan bir değişken varsa, eski değeri mi yoksa yeni değeri mi kullandığını nasıl anlarsınız?

# 1 örneği olarak, ifadede

x = x++ + ++x;

x'i değiştirmek için üç deneme vardır.

# 2 örneği olarak, ifadede

y = x + x++;

ikimiz de değerini kullanırız xve değiştiririz.

Cevap budur: yazdığınız herhangi bir ifadede, her değişkenin en fazla bir kez değiştirildiğinden emin olun ve bir değişken değiştirilirse, bu değişkenin değerini başka bir yerde de kullanmaya çalışmayın.


3

Bunun nedeni, programın tanımsız davranış çalıştırmasıdır. Sorun, değerlendirme düzeninde yatmaktadır, çünkü C ++ 98 standardına göre gerekli dizi noktası yoktur (C ++ 11 terminolojisine göre bir işlemden önce veya sonra hiçbir işlem sıralanmaz).

Ancak bir derleyiciye bağlı kalırsanız, işlev çağrıları veya işaretçiler eklemediğiniz sürece, davranışı daha dağınık hale getirecek şekilde davranışı kalıcı bulacaksınız.

  • İlk önce GCC: Nuwen MinGW 15 GCC 7.1 kullanarak şunları elde edersiniz:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2

    }

GCC nasıl çalışır? alt ifadeleri sağ taraf için (RHS) soldan sağa doğru değerlendirir, ardından değeri sol tarafa (LHS) atar. Java ve C # tam olarak bu şekilde davranır ve standartlarını tanımlar. (Evet, Java ve C # 'daki eşdeğer yazılım davranışları tanımlamıştır). Her alt ifadeyi soldan sağa doğru RHS Bildirimi'nde tek tek değerlendirir; her alt ifade için: ++ c (artım öncesi) önce değerlendirilir, daha sonra işlem için c değeri, sonra artış + sonrası ++ kullanılır.

uygun ++ GCC C: Operatörler

GCC C ++ 'da operatörlerin önceliği, münferit operatörlerin değerlendirilme sırasını kontrol eder

GCC'nin anladığı gibi C ++ tanımlı davranışındaki eşdeğer kod:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Sonra Visual Studio'ya gidiyoruz . Visual Studio 2015, şunları elde edersiniz:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Görsel stüdyo nasıl çalışır, başka bir yaklaşım alır, ilk geçişte tüm artışlar ifadelerini değerlendirir, daha sonra ikinci geçişte işlemlerde değişken değerleri kullanır, üçüncü geçişte RHS'den LHS'ye atar, son olarak tüm geçişleri değerlendirir tek geçişte artım sonrası ifadeler.

Yani Visual C ++ olarak tanımlanan C ++ davranışında eşdeğer:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

Visual Studio belgelerinin Öncelik ve Değerlendirme Sırasında belirttiği gibi :

Birkaç operatörün birlikte göründüğü yerde, eşit önceliğe sahiptirler ve ilişkilendirilebilirliklerine göre değerlendirilirler. Tablodaki operatörler Postfix Operatörleri ile başlayan bölümlerde açıklanmaktadır.


1
Bu soru genellikle bunun için bir kopya olarak kullanıldığından, işlev argümanlarının değerlendirilmesinde UB'yi eklemek için soruyu düzenledim. (Son örnek)
Antti Haapala

1
Ayrıca soru şimdi c ile ilgili , C ++ değil
Antti Haapala
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.