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 ++x
postfix 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 x
ilk, ya x++
da belki de değerini hesaplıyor ++x
mu? 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 ++x
sonra x
veya 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 x
ve eski değerini döndürmeye çalışır x
; (2) ++x
parça x'e 1 eklemeye, yeni değeri depolamaya ve yeni değerini x
dö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:
- İ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?
- 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 x
ve 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.