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

Jun 04 2009
#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?
}

Yanıtlar

573 unwind Jun 04 2009 at 16:20

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, tanımsız davranış kavramının neden var olduğunu açıkça söylemiyor . Bana göre, bu basitçe, dil tasarımcılarının anlambilimde bir boşluk olmasını istedikleri için, yani tüm uygulamaların tamsayı taşmasını tam olarak aynı şekilde ele almasını gerektirmek yerine, ki bu da büyük olasılıkla ciddi performans maliyetleri doğurur, sadece davranışı terk ettiler tanımsızdır, böylece tamsayı taşmasına neden olan bir kod yazarsanız, her şey olabilir.

Öyleyse, bunu akılda tutarak, neden bu "sorunlar"? Dil, belirli şeylerin tanımlanmamış davranışlara yol açtığını açıkça söylüyor . Sorun yok, işin içinde "gerekir" 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 mantık yürütemezsin.

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

u = (u++);

tanımlanmamış davranışın bir metin kitabı örneğidir (Wikipedia'nın sıra noktalarına ilişkin girişine bakın ).

76 badp May 24 2010 at 20:26

Sadece kod satırınızı derleyin ve parçalara ayırın, eğer aldığınız şeyi tam olarak nasıl elde ettiğinizi bilme eğilimindeyseniz.

Bu, olduğunu düşündüğüm şeyle birlikte makinemde aldığım şey:

$ 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 ... 0x00000014 talimatının bir tür derleyici optimizasyonu olduğunu varsayıyorum?)

66 Christoph Jun 04 2009 at 16:35

C99 standardının ilgili kısımlarının 6.5 İfadeler olduğunu düşünüyorum, §2

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

ve 6.5.16 Atama operatörleri, §4:

İşlenenlerin değerlendirme sırası belirtilmedi. Bir atama işlecinin sonucunu değiştirme veya sonraki sıra noktasından sonra ona erişme girişiminde bulunulursa, davranış tanımsızdır.

61 haccks Jun 27 2015 at 07:27

Buradaki cevapların çoğu, bu yapıların davranışının tanımsız olduğunu vurgulayan C standardından alıntılanmıştı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.

Sırasız:

Eğer Aönce veya sonra sıralandı edilmez B, sonra Ave Bunsequenced bulunmaktadır.

Değerlendirmeler iki şeyden biri olabilir:

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

Sıra 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 geliyor, gibi ifadeler için

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 çağırır, çünkü aynı nesne üzerindeki iki yan etki birbirine göre sıralanmaz. Bu, atama yoluyla yan etkinin, yan etkiden iönce mi yoksa sonra mı yapılacağı sıralanmadığı anlamına gelir ++.
Atamanın artımdan önce mi sonra mı gerçekleştiğine bağlı olarak, farklı sonuçlar üretilecektir ve bu, tanımlanmamış davranış durumlarından biridir .

iAtamanın solundaki be ilve atamanın sağındaki (ifadede i++) be olarak yeniden adlandıralım ir, sonra ifade şöyle olsun

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 . Derleyici orijinal değerin kullanılmasını sağladığı sürece artış, derleyicinin istediği kadar erken gerçekleşebilir .

İfadenin 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çla sonuçlanır 1ve 2bu, atamaya göre yan etkilerin sırasına bağlıdır ++ve dolayısıyla UB'yi çağırır.

54 ShafikYaghmour Aug 16 2013 at 02:25

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 ortamla çok özel durumlarda tahmin eder, ancak lütfen bunu üretimin yakınında hiçbir yerde yapmayın.

Bu nedenle, belirlenmemiş davranışa geçersek , taslak c99 standart bölüm 3.6.5 paragrafta şöyle diyor ( vurgu benim ):

Operatörlerin ve işlenenlerin gruplandırılması sözdizimi ile belirtilir. 74) Daha sonra belirtilmediği sürece (fonksiyon çağrısı (), &&, ||,?: Ve virgül operatörleri için), alt ifadelerin değerlendirme sırası ve hangi yan etkilerin meydana geldiği belirtilmemiştir.

Öyleyse böyle bir satırımız olduğunda:

i = i++ + ++i;

biz mi bilmiyorum i++ya ++iilk değerlendirilecektir. Bu esas olarak derleyiciye optimizasyon için daha iyi seçenekler sağlamak içindir .

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 2.6.5 paragraftan ( vurgu benim ):

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

aşağıdaki kod örneklerini tanımsız olarak gösterir:

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

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

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

i = (i++);
^    ^

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

u = (u++);
^    ^

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

Tanımlanmamış davranış tanımlanan taslak c99 standardı bölümünde 3.4.4gibidir:

Belirtilmemiş bir değerin kullanılması veya bu Uluslararası Standardın iki veya daha fazla olasılık sağladığı ve herhangi bir durumda seçilecek başka herhangi bir şart koymadığı durumlarda diğer davranışlar

ve tanımlanmamış davranış , bölümde şu şekilde tanımlanır 3.4.3:

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

ve şunları not eder:

Olası tanımlanmamış davranış, durumu tamamen öngörülemeyen sonuçlarla göz ardı etmekten, çeviri veya program yürütme sırasında çevrenin özelliği olan belgelenmiş bir şekilde davranmaya (bir tanılama mesajı vererek veya vermeden), bir çeviri veya yürütmeyi sonlandırmaya (yayınlama ile bir teşhis mesajı).

38 SteveSummit Jun 18 2015 at 18:55

Sıra noktalarının ve tanımlanmamış davranışların gizli ayrıntılarıyla boğuşmak yerine, buna cevap vermenin başka bir yolu, basitçe sormaktır, ne anlama geliyorlar? Programcı ne yapmaya çalışıyordu?

Sorulan ilk parça, i = i++ + ++ikitabımda oldukça açık bir şekilde çılgınca. Hiç kimse bunu gerçek bir programda yazmazdı, ne yaptığı belli değil, birinin kodlamaya çalıştığı, bu belirli işlem dizisiyle sonuçlanabilecek akla gelebilecek bir algoritma yok. Ve ne yapması gerektiği sizin ve benim için açık olmadığından, derleyicinin ne yapması gerektiğini çözememesi benim kitabımda sorun değil.

İkinci parçanın i = i++anlaşılması biraz daha kolay. Birisi açıkça i'yi artırmaya ve sonucu tekrar i'ye atamaya çalışıyor. Ama bunu C'de yapmanın birkaç yolu var. 1'i i'ye eklemenin ve sonucu i'ye geri atamanın en basit yolu, hemen hemen her programlama dilinde aynıdır:

i = i + 1

C'nin elbette kullanışlı bir kısayolu var:

i++

Bu, "i'ye 1 ekleyin ve sonucu i'ye geri atayın" anlamına gelir. Öyleyse, ikisinden oluşan bir karmaşa oluşturursak,

i = i++

gerçekten söylediğimiz şey "1’i i’ye ekle ve sonucu i’ye geri at ve sonucu tekrar i’ye ata". Kafamız karıştı, bu yüzden derleyicinin de kafasının karışması beni çok fazla rahatsız etmiyor.

Gerçekçi olarak, bu çılgın ifadelerin yazıldığı tek zaman, insanların bunları ++ 'nın nasıl çalışması gerektiğinin yapay örnekleri olarak kullandıkları zamandır. Ve tabii ki ++ 'nın nasıl çalıştığını anlamak önemlidir. Ancak ++ kullanmanın pratik kurallarından biri şudur: "++ kullanan bir ifadenin ne anlama geldiği açık değilse, onu yazmayın."

Comp.lang.c üzerinde bu tür ifadeleri ve neden tanımsız olduklarını tartışmak için sayısız saatler harcardık . Nedenini gerçekten açıklamaya çalışan uzun yanıtlarımdan ikisi web'de arşivlendi:

  • Standart bunların ne yaptığını neden tanımlamıyor?
  • Operatör önceliği, değerlendirme sırasını belirlemiyor mu?

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

27 P.P Dec 31 2015 at 03:26

Genellikle bu soru, kodla ilgili soruların bir kopyası olarak bağlantılıdır.

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

veya

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

veya benzer varyantlar.

Bu aynı zamanda daha önce belirtildiği gibi tanımlanmamış bir davranış olsa da , aşağıdaki printf()gibi bir ifadeyle karşılaştırıldığında dahil edildiğinde ince 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ş . Bu, ifadeler anlamına gelir i++ve ++iherhangi bir sırayla değerlendirilebilir. C11 standardının bununla ilgili bazı açıklamaları vardır:

Ek J, belirtilmemiş davranışlar

Bağımsız değişkenler içindeki işlev belirteci, bağımsız değişkenler ve alt ifadelerin bir işlev çağrısında değerlendirildiği sıra (6.5.2.2).

3.4.4, belirtilmemiş davranış

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

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

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

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

Bu da sahiptir belirtilmemiş davranışı değerlendirilmesi sırası nedeniyle ++xve y++belirsizdir. Ama bu tamamen yasal ve geçerli bir ifade. Orada hiçbir bu açıklamada tanımsız davranış. Çünkü değişiklikler ( ++xve y++) farklı nesnelere yapılır .

Aşağıdaki ifadeyi oluşturan şey

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 geçerli kılan bir sıra 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ü, soldan sağa işlenenlerini 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. Daha sonra artım sonrası nedeniyle 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 buradaki virgül bir ayırıcı olduğu için tanımsız davranışı çağırır .


Tanımlanmamış davranış konusunda yeni olanlar , C'deki tanımsız davranışın kavramını ve diğer birçok varyantını anlamak için Her C Programcısının Tanımlanmamış Davranış Hakkında Bilmesi Gerekenler'i okumaktan fayda sağlayacaktır .

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

23 supercat Dec 06 2012 at 01:30

Herhangi bir derleyicinin ve işlemcinin gerçekten bunu yapması pek olası olmasa da, C standardına göre derleyicinin "i ++" dizisini şu sırayla uygulaması yasal olacaktır:

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üşünmüyorum, ancak bu tür bir davranışın çok iş parçacıklı kodu kolaylaştıracağı durumları kolayca hayal edebilirim (örneğin, iki iş parçacığının yukarıdakileri gerçekleştirmeye çalışması garanti olur. eşzamanlı olarak, iiki kat artacaktır) ve gelecekteki bazı işlemcilerin böyle bir özellik sağlayabileceği tamamen düşünülemez değildir.

Derleyici i++yukarıda belirtildiği gibi yazacaksa (standart uyarınca yasal) ve genel ifadenin (aynı zamanda yasal) değerlendirmesi boyunca yukarıdaki talimatları karıştıracaksa 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)(kullanmak yerine yukarıdaki ifadede iiki kez) derleyicinin aynı nesnenin adresi hem pve hem de için iletilmesi durumunda ortaya çıkacak kilitlenmeyi tanıması veya engellemesi gerekmezdi q.

18 AnttiHaapala Mar 26 2017 at 21:58

İ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 sıra noktası arasında, bir nesnenin depolanmış değeri, bir ifadenin değerlendirilmesiyle en fazla bir kez değiştirilecektir. [72] Ayrıca, önceki değer sadece saklanacak değeri belirlemek için okunmalıdır [73]

73 no'lu dipnot ile ,

  1. Bu paragraf, aşağıdaki gibi tanımsız ifade ifadelerini oluşturur

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

    izin verirken

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

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

  1. Aşağıdakiler 5.1.2.3'te açıklanan sıra noktalarıdır:

    • Fonksiyon göstericisinin değerlendirmeleri ile bir fonksiyon çağrısındaki gerçek argümanlar ve gerçek çağrı 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 operandın değerlendirmeleri arasında? : operatör ve ikinci ve üçüncü işlenenlerden hangisi değerlendirilirse (6.5.15).
    • Tam bir bildiricinin sonu: bildiriciler (6.7.6);
    • Tam ifadenin değerlendirilmesi ile değerlendirilecek sonraki tam ifade arasında. Aşağıdakiler tam ifadelerdir: bir bileşik hazır bilgisinin parçası olmayan bir başlatıcı (6.7.9); bir ifade ifadesindeki ifade (6.8.3); bir seçim ifadesinin kontrol edici ifadesi (if veya switch) (6.8.4); while veya do ifadesinin kontrol edici ifadesi (6.8.5); for ifadesinin (6.8.5.3) her biri (isteğe bağlı) ifadesi; dönüş ifadesindeki (isteğe bağlı) ifade (6.8.6.4).
    • Bir kütüphane işlevi (7.1.4) dönmeden hemen önce.
    • Biçimlendirilmiş her girdi / çıktı işlevi dönüştürme belirticisi (7.21.6, 7.29.2) ile ilişkili eylemlerden sonra.
    • Bir karşılaştırma işlevine yapılan her çağrıdan hemen önce ve hemen sonra ve ayrıca bir karşılaştırma işlevine yapılan herhangi bir çağrı ile bu çağrıya argüman olarak iletilen nesnelerin herhangi bir hareketi arasında (7.22.5).

C11'deki aynı paragrafın ifadesi şöyledir:

  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 verilen sıralaması varsa, sıralamalardan herhangi birinde böyle sıralanmamış bir yan etki meydana gelirse davranış tanımsızdır.84)

Örneğin ile GCC son sürümünü kullanarak bir programda tür hataları tespit edebilir -Wallve -Werrorve sonra GCC düpedüz derlenmesini reddedecektir. Aşağıdakiler 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 olan, sıralama noktasının ne olduğunu ve sıra noktasının ne olduğunu ve neyin olmadığını bilmektir . Örneğin, virgül operatörü bir sıra noktasıdır, bu nedenle

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

iyi tanımlanmıştır ve bir artarak ieski değeri verir, bu değeri atar; sonra virgül operatöründe yan etkileri giderin; ve sonra bir artar ive sonuçta ortaya çıkan değer ifadenin değeri haline gelir - yani bu, yazmanın j = (i += 2)yine "akıllı" bir yolu olan yazmanın sadece uydurma bir yoludur

i += 2;
j = i;

Ancak, ,işlev içi bağımsız değişken listeleri virgül operatörü değildir ve farklı bağımsız değişkenlerin değerlendirmeleri arasında sıra noktası yoktur; bunun yerine değerlendirmeleri birbirleriyle ilgili olarak sıralanmamış; yani işlev çağrısı

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

yer alır tanımsız davranış nedeniyle orada değerlendirmeler arasında bir dizi nokta i++ve ++ifonksiyon bağımsız değişkenleri ve değeri i, bu nedenle her ikisi ile, iki kez değiştirilmiş i++ve ++ibir önceki ve bir sonraki sıra noktası arasında,.

14 NikhilVidhani Sep 11 2014 at 19:36

C standardı, bir değişkenin iki sıra noktası arasında en fazla bir kez atanması gerektiğini söyler. Bir noktalı virgül, örneğin bir sıra noktasıdır.
Yani formun her ifadesi:

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

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

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

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

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

11 TomOnTime Apr 08 2015 at 10:20

İçinde https://stackoverflow.com/questions/29505280/incrementing-array-index-in-c birisi şöyle bir ifade sordu:

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);

Bu 7 yazdırır ... OP, 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 edecek. Aslında, muhtemelen ilk yazdığınız koddan daha iyidir (umduğunuz şekilde çalıştığını varsayarak).

6 SteveSummit Aug 16 2018 at 18:54

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

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

Şimdiye kadar C'lerin ++ve --operatörlerin temel tanımını ve önek formunun ++xsonek 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 aşağıdakiler gibi bir şey içeren küçük bir test programı yazmışsınızdır:

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.

Veya belki de anlaşılması zor bir ifadeye bakıyorsunuz

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

Belki birisi size bu kodu bilmece olarak vermiştir. Bu kod aynı zamanda bir anlam ifade etmiyor, özellikle çalıştırırsanız - ve eğer onu iki farklı derleyici altında derleyip çalıştırırsanız, muhtemelen iki farklı cevap alırsınız! Buna ne oluyor? Hangi cevap doğru? (Ve cevap şu ki, ikisi de öyle ya da hiç değil.)

Şimdiye kadar duyduğunuz gibi, tüm bu ifadeler tanımsızdır , bu da C dilinin ne yapacakları konusunda hiçbir garanti vermediği anlamına gelir. Bu tuhaf ve şaşırtıcı bir sonuç, çünkü muhtemelen 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, öyle değil.

Bir ifadeyi tanımsız yapan nedir? İfadeler içerir ++ve --her zaman tanımsız mıdır? Tabii ki hayır: bunlar yararlı operatörler ve eğer onları doğru kullanırsanız, mükemmel şekilde tanımlanmışlardır.

Bahsettiğimiz ifadeler için, onları tanımlanmamış kılan şey, aynı anda çok fazla şey olduğunda, şeylerin hangi sırayla olacağından emin olmadığımızda, ancak aldığımız sonuç için sıra önemli olduğunda.

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

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

soru şu ki, çağırmadan önce printfderleyici xilk değerin değerini mi hesaplıyor x++, yoksa belki ++x? Ama bizim bilmediğimiz ortaya çıktı . C'de bir fonksiyonun argümanlarının soldan sağa, sağdan sola veya başka bir sırada değerlendirildiğ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 sıra açıkça önemlidir, çünkü derleyicinin hangi sırayı kullandığına bağlı olarak, açıkça farklı sonuçlar basarız printf.

Peki ya bu çılgın ifade?

x = x++ + ++x;

Bu ifadenin sorunu, x'in değerini değiştirmek için üç farklı girişim içermesidir: (1) x++parça 1'i x'e eklemeye, yeni değeri içinde saklamaya xve eski değerini döndürmeye çalışır x; (2) ++xparça x'e 1 eklemeye, yeni değeri içine kaydetmeye ve yeni değerini xdöndürmeye çalışır x; ve (3) x =parça diğer ikisinin toplamını x'e geri atamaya çalışır. Denenen bu üç görevden hangisi "kazanacak"? Gerçekte üç değerden hangisine atanacak x? Yine ve belki de şaşırtıcı bir şekilde, C'de bize söylenecek bir kural yoktur.

Önceliğin veya çağrışımsallığın veya soldan sağa değerlendirmenin size olayların hangi sırayla gerçekleştiğini söylediğini hayal edebilirsiniz, ancak bunlar yapmaz. Bana inanmayabilirsiniz, ama lütfen sözümü alın ve tekrar söyleyeceğim: Öncelik ve çağrışım, C'deki bir ifadenin değerlendirme sırasının her yönünü belirlemez. Özellikle, bir ifadede birden çok ifade 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.


Öyleyse, tüm bu arka plan ve girişin dışında, 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 gayet 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++);

Ve son soru, hangi ifadelerin iyi tanımlandığını ve hangi ifadelerin tanımsız olduğunu nasıl anlarsınız?

Daha önce de söylediğim gibi, tanımlanmamış ifadeler, aynı anda çok fazla şeyin olduğu, olayların hangi sırayla gerçekleştiğinden emin olamadığınız ve sıranın nerede önemli olduğu durumlardır:

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

İfadede # 1 örneği olarak

x = x++ + ++x;

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

İfadede # 2 örneği olarak

y = x + x++;

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

İşte cevap bu: yazdığınız herhangi bir ifadede, her değişkenin en fazla bir kez değiştirildiğinden emin olun ve eğer bir değişken değiştirilirse, o değişkenin değerini başka bir yerde de kullanmayı denemeyin.

5 alinsoar Oct 13 2017 at 20:58

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

Fikirleri açıklarım.

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

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

Bir ifadedeki sıra noktaları i=i++, öncesi i=ve sonrası gibi i++.

Yukarıda aktardığım yazıda, programın her biri 2 ardışık sıra noktası arasındaki talimatları içeren küçük kutulardan oluştuğunu anlayabileceğiniz anlatılıyor. i=i++Bir tam ifadeyi sınırlayan 2 sıra noktası olması durumunda, sıra noktaları standardın Ek C'de tanımlanmıştır . Böyle bir ifade, expression-statementdilbilgisinin Backus-Naur formundaki girişiyle sözdizimsel olarak eşdeğerdir (bir dilbilgisi Standart Ek A'da verilmiştir).

Bu nedenle, bir kutudaki talimatların sırasının net bir sırası yoktur.

i=i++

olarak yorumlanabilir

tmp = i
i=i+1
i = tmp

veya olarak

tmp = i
i = tmp
i=i+1

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

Dolayısıyla, programı oluşturan her kutunun başında ve sonunda bir sıra noktası görülebilir [kutular C'deki atomik birimlerdir] ve bir kutu içinde komutları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 kaynaklar, c-faq sitesinden ( kitap olarak da yayınlanmıştır ), yani burada , burada ve buradaki girişlerdir .

3 MohamedEl-Nakib Jun 11 2017 at 05:56

Bunun nedeni, programın tanımsız davranışlar sergiliyor olmasıdır. Sorun değerlendirme sırasındadır, çünkü C ++ 98 standardına göre gereken sıra noktası yoktur (C ++ 11 terminolojisine göre hiçbir işlem diğerinden önce veya sonra sıralanmaz).

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

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

    #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? sağ taraf (RHS) için soldan sağa sırayla alt ifadeleri 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ın tanımlanmış davranışları vardır). RHS Bildirimindeki her alt ifadeyi soldan sağa sırayla tek tek değerlendirir; her alt ifade için: önce ++ c (artış öncesi) değerlendirilir, ardından işlem için c değeri, ardından artım c ++).

uygun ++ GCC C: Operatörler

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

GCC'nin anladığı gibi tanımlanmış C ++ 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
}

Ardından Visual Studio'ya geçiyoruz . Visual Studio 2015 ile ş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 benimser, ilk geçişte tüm ön artırım ifadelerini değerlendirir, ardından ikinci geçişte işlemlerde değişken değerlerini kullanır, üçüncü geçişte RHS'den LHS'ye atama ve son geçişte tüm değerleri değerlendirir. Arttırma sonrası ifadeler tek geçişte.

Dolayısıyla, Visual C ++ 'nın anladığı gibi tanımlanmış C ++ davranışındaki 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 belgelerinde Öncelik ve Değerlendirme Sırası'nda belirtildiği gibi :

Birkaç operatörün birlikte göründüğü yerlerde, bunlar eşit önceliğe sahiptir ve ilişkilendirilebilirliklerine göre değerlendirilir. Tablodaki operatörler, Postfix Operatörleri ile başlayan bölümlerde açıklanmıştır.