C++ kodu farklı çıktı veriyor

Böyle dener misin?
C++:
cout << (++a) * (--b) << endl;

Screenshot_2.png



Bu problemin sebebi ++a yaptığınzda a değişkeni 6 oluyor. Fakat b-- yaptığında b değişkeni kullanıldıktan sonra 2 oluyor.

Yani bu problemi yaşama sebebiniz program bunu (6 * 3) olarak algıladığından kaynaklı.
 
a++ ile yaparsan o kod satırındaki işlemler bittikten sonra a, 1 arttırılır.
++a ile yaparsan o kod satırındaki işlemlerinin bitmesi beklenmeden a, 1 arıttırılır.

Satır bittikten sonra değil, kullanıldıktan önce veya sonraya göre artış/düşüş yaşanıyor.
Mesela aşağıdaki örnekte çıktı "3 4" olacak.

C++:
int b = 3;
cout << b++ << " " << b  << endl;
 
Öncelikle öyle kod paylaşma.

Olaya gelecek olursak cout'un std::cout olduğunu, aynı şekilde endl'ın std::endl olduğunu varsayıyorum.
Buradan da öğrenebileceğin gibi ++a, 6 döndürken b-- 3 döndürecek. 6*3 de 18.

++a şuna eşdeğer işi yapar.
Kod:
int* function(int* a) {
    *a += 1;
    return a;
}
b-- ise şuna eşdeğer işi yapar.
Kod:
int* function(int *b){
    int temp = *b;
    *b -= 1;
    return &temp;
}

Örneğin ++a + (++a * ++b) yazarsanız 7 + (7 * 4) yazmış olursunuz. Çünkü ++a lvalue referans döndürür. a++ ise prvalue döndürür.
Üsttekini yazabilirsiniz ama alttakini yazamasınız.
Kod:
++a = 5;
b++ = 3;

++ operatörü o kadar basit bir şey değil. Bu yüzden basit döngülerde i++ yerine ++i öneriyoruz ama dinleyen yok.

Başka bir örnek daha vereyim. Parantezler aritmetik gruplamayı sağlar. Operatörleri ellemezler. Kaynaktaki tablonun sağ sütununda görebilirsiniz.
İlk satır güzel güzel üçünü artırıp sonra işlemi yapıp 132 döndürecektir. İkinci satırda ise soldan sağa şekilde hesaplanacak. İlki 3 döndürüp b'yi 4 yapacak. (Evet, parantez içi olmasına rağmen önce soldaki hesaplanıyor) İkincisi 4 döndürüp 5 yapacak, üçüncüsü de 5 döndürüp 6 yapacak ve 23 değeri elde edilecek.
Üçüncü satıra b 6 olarak gelecek. İlki 6 döndürüp 7 yapacak. İkinci 7 döndürüp 8 yapacak. Üçüncü 8 döndürüp 9 yapacak. Bu sayede 50 gelecek.
Kod:
std::cout << ++a + (++a * ++a) << std::endl;
std::cout << b++ + (b++ * b++) << std::endl;
std::cout << (b++ * b++) + b++ << std::endl;

Farklı dillerde farklı şeyler söz konusu olabilir. C++ böyle. Şimdi konudaki yanlışlara gelelim.
Böyle dener misin?
Öyle yaparsa öyle sonuç alır. Konuda onu sormuyor.

a++ ile yaparsan o kod satırındaki işlemler bittikten sonra a, 1 arttırılır.
++a ile yaparsan o kod satırındaki işlemlerinin bitmesi beklenmeden a, 1 arıttırılır.
Örneğimde de belirttiğim gibi ilk cümleniz yanlış. İkisi de anında değeri artırır.
std::cout << a++ + (++a * ++a) << std::endl; ifadesini çalıştırırsanız görebilirsiniz. Kaynaktaki tabloda görebileceğiniz gibi a++ daha öncelikli olduğu için önce o çalışır ve 8 değerini döndürüp a'yı 9 yapar. Sonra ise kalanlar çalışıp 11*11 yapar.

a'yı bir arttırmak istiyorsan a++ yazmalısın.
Hayır. Amaç sadece değeri artırmaksa ++a şeklinde yazılmalı.

Satır bittikten sonra değil, kullanıldıktan önce veya sonraya göre artış/düşüş yaşanıyor.
Kullanıldıktan önce/sonradan kastınız prefix/postfix ise operatör önceliğine göre. std::cout << ++a + a + ++a << std::endl; ile std::cout << a + ++a + ++a << std::endl; arasında değer olarak fark yok. İlkinde önce ++a var diye sadece a olanın değeri değişmiyor.

Assembly kodunda 12 ve 13. satırlar C++ kodunda 5 ve 6. satırlara denk geliyor. Görebileceğiniz gibi aralarında hiçbir fark yok. İkisinin de tek yaptığı a değerini bir artırmak. Optimizasyon kapatılsa bile aynı çünkü compiler'ların kendi söyledikleri gibi isterlerse kendileri belirli seviye optimizasyon yaparlar.

Ama C++ kodunda 2 ve 3. satırlara bakarsak birinin assembly'de 5, 6, 7 şeklinde 3 komut ile yapılırken diğerinin 8, 9, 10 ve 11 şeklinde 4 komut ile yapıldığını görüyoruz.

Assembly 5. satırda a'nın değerini (rbp - 20 adresi) 1 artırıyor. 6. satırda bunu eax'e atıyor. 7. satırda eax'tekini b'ye (rbp - 4 adresi) aktarıyor.
eax 32 bitlik bir register. ax register'ının büyük hali. x86'da bellekten belleğe aktarım yapılmadığı için böyle aktarım sağlıyor. a'dan b'ye gitmek yerine a'dan işlemciye, işlemci'den b'ye gidiliyor.
Bu sayede bir artırma ve 2 taşıma komutuyla a değerimizi 1 artırmış olup b'ye o değeri vermiş olduk.

Assembly 8. satırda a'daki değeri eax'e atıyor. Sonra hile yapıp lea kullanıyor (bu sayede daha az satır kullanmış oluyor). 9. satırda gidiyor geçici değişken olarak edx register'ına rax + 1'i atıyor. rax eax'ın daha da büyük (bkz. 64 bit) hali olduğu için içinde aslında eax, yani a değişkenimizin artmamış değeri var. lea bunu artırıp edx'e atıyor. Sonraki iki satır zaten belli. Önce edx'ten artmış değeri a'ya atıyor, sonra da eax ile artmamış değeri c'ye atıyor.
Gördüğünüz gibi basit bir artırma gibi gözükse de değerin direkt artmaması çok zahmetli. İşlemcinizi boşuna yoruyorsunuz. Üstüne biraz karışık şekilde kullandığınız an işler anlaşılmaz hale geliyor.

1673028726913.png
 
Son düzenleme:

Yeni konular

Geri
Yukarı