Herhalde yaklaşık 6 senedir C/C++ kullanıyorum. Çok büyük ve uzun süreli projelere katılmamış olsam da kendimi bir miktar yetkin görürüm. Tez için bir projede çalışırken nedense daha önce pek detaylı araştırmadığım bir konunun farkına vardım: ardışıklık konusu.

Aşağıdaki kod parçalarının nasıl çalışacağından emin misiniz?
x = x++;
(x = 1) * (x = 2);
(x = 1) * x;
f(), g();
f() * g();

Bir değişkenin ne zaman değerinin alınacağı ("fetch" edileceği) veya değer atanacağı ("set" edileceği) tam olarak bilinmediğinde işlem belirsiz oluyor, etkilerin ne olacağı belirlenemiyor ("race condition" ortaya çıkıyor). Örneğin ilk örnek olan "x = x++"da x daha önce diyelim ki 1 iken, bu işlem sonunda ne olacaktır? Buradaki ++ operatörü sonra geldiği için değer x'in önceki değeri olacak, x artırılacak ve sonra bu değer x'e atanacak. Peki atanma ve artırma olaylarının sırası belirli mi? Cevap belirli olmadığı yönünde. Bu küçük kodu derleyicide derleyip birçok kez çalıştırdığımda cevap her seferinde 2 çıktı. Bu durumda sanki x değerinin arttırılması daha sonra yapılıyormuş gibi görünüyor. Ama bu yanıltıcı bir düşünce, çünkü daha mantıklı gelen (intuitive) başka kod parçalarında diğer düşünce doğru geliyor. Ne olursa olsun bu türden kodlar yazmamak gerekiyor.

İkinci ve üçüncü örneklerde çarpım yapılıyor. Sorun burada şu ki C/C++'da çarpım yapılırken değerlerin hangi sırayla belirleneceğı belirli değil. Standart bunu tanımlamıyor, dolayısıyla derleyici üreticileri bu konuda özgürler. Yani demek istediğim şu ki (x = 1)'in mi önce yoksa (x = 2)'nin mi önce çalışacağı belli değil. Kod yazarken bu türden işlemler yazmamak gerekiyor, yani hangisinin önce çalıştırılacağı üzerine varsayım yapmamak, bunun işlemi etkilememesi gerekiyor.

Kullandığım dördüncü örnek aslında sorunsuz. C'de virgül operatörü kullandığınızda sıra bellidir. Soldan sağa doğru çalıştırılır ve yalnızca sonuncunun değeri alınır. Dolayısıyla burada ne olacağı belirli.

Son örnek ise yine çarpım örneği ve fonksiyonları kullanıyor. Burada da çarpımdaki belirsizlik var. Ancak birşey daha var ki, C'de fonksiyon çağırma iç içe (interleaved) olamıyor. Dolayısıyla biri bittikten sonra diğeri başlayacak. Bir tür atomiklik (atomicity) var. Yine de bu hangisinin önce başlayıp biteceğini bize söylemiyor tabii ki. Dolayısıyla bu türden kod parçalarını da kullanmamalıyız.

Bu ardışıklık konusunda daha fazla bilgi için birçok makale var. İlginizi çekiyorsa bir bakın derim. Ama sadece sonuçlarla ilgiliyseniz fazla dalmayın bu konuya, oldukça detaylı çünkü. Bundan başka derleyicinin size bu konuda yardımcı olmasını istiyorsanız bu adresteki -Wsequence-point seçeneğine bir bakın derim. GCC'nin size ne kadar yardım edebileceğine şaşırabilirsiniz.

Şimdi ben bu konuya nereden dalmıştım... Elimde şöyle bir kod parçası var:
map::iterator it;
for(it = myMap.begin(); it != myMap.end(); it++) {
if (it->second < threshold)
myMap.erase(it);
}

Burada bir map yapısındaki sınır değerinden düşük elemanlar silinmek isteniyor. Ama bu program segmentation fault verecektir. Sorun şu ki iterator'ı önce siliyoruz sonra da artırarak ilerlemeye çalışıyoruz. Bu durumda silinmiş bir objeye ulaşmaya çalıştığımız için de işletim sistemi kafamıza vuracaktır. Doğru kod şöyle olmalıydı:
map::iterator it;
for(it = myMap.begin(); it != myMap.end(); ) {
if (it->second < threshold)
myMap.erase(it++);
else it++;
}

Böylece silmeden önce it objesi artırılıyor, silerken eski değerin gösterdiği yer siliniyor ve biz yolumuza devam ediyoruz. Mantıklı değil mi? :)