Değerlendirilme sırası
Neredeyse tüm C++ operatör işlenenlerinin değerlendirilme sırası (bir fonksiyon çağrısındaki parametrelerin değerlendirilme sırası ve herhangi bir ifade içindeki alt-ifadelerin değerlendirilme sırası da dahil) belirsizdir. Derleyici, işlenenleri herhangi bir sıra ile değerlendirebilir. Hatta aynı ifadenin tekrar eden bir sonraki değerlendirmelerinde başka bir sıralama da tercih edebilir.
Aşağıda belirtildiği üzere bu kuralın istisnaları vardır.
Aşağıda belirtilen durumlar dışında C++'da soldan-sağa ya da sağdan-sola değerlendirme şeklinde bir kavram bulunmamaktadır. Bu, operatörlerin soldan-sağa ya da sağdan-sola ilişkilendirilebilirliğiyle karıştırılmamalıdır: örneğin takip eden f1() + f2() + f3()
ifadesi derleyiciler tarafından operator+'nın soldan-sağa ilişkilendirilebilirliği sebebiyle (f1() + f2()) + f3()
şeklinde çözümlenmektedir. Ancak burada f3
fonksiyon çağrı ifadesi çalışma zamanında önce, sonra ya da f1()
ifadesi ve f2()
ifadesi arasında değerlendirilebilir.
Konu başlıkları |
[düzenle] Önüne-sıralanma kuralları (C++11'den beri)
[düzenle] Tanımlar
[düzenle] İfade değerlendirme türleri
Her bir ifade ya da alt-ifade için derleyiciler tarafından işlettirilen iki tür 'ifade değerlendirme' söz konusudur (Derleyici açısından her ikisi de seçimliktir, derleyicinin bir ifadeyi değerlendirmesi sonucu 'yan etki' ve 'değer hesaplama' işleri birlikte gerçekleşebilir, veya sadece biri gerçekleşirken diğeri gerçekleşmeyebilir. Her ifade değerlendirme sonucu iki durum da birden oluşmayabilir):
- değer hesaplama: bir ifade tarafından döndürülen değerin hesaplanmış olmasına verilen addır. Buna, nesnenin hangi tür olduğunun derleyici tarafından tespit edilmesi veya nesneye bir önceki erişim esnasında atanan değerin okunması da dahildir.
- yan etki: Bilgisayar biliminde bir fonksiyon veya ifadenin, kendi kapsamı dışında kalan bazı verileri değiştirmesi veya yalnızca bir değer döndürmek yerine kendini çağıran fonksiyonla veya dış dünyayla gözlemlenebilir bir etkileşimde bulunması durumuna o fonksiyon veya ifadenin 'yan etkisi' denir. Örneğin, belirli bir fonksiyon yada ifade, global veya statik bir veriyi değiştirir, kendi parametrelerinden birini değiştirir, bir istisna fırlatır, ekrana veya bir dosyaya veri yazar, verileri okur veya diğer yan etkilere neden olan fonksiyonlar çağırır. Yan etkilerin varlığında, bir programın davranışı geçmişe bağlı olabilir; yani değerlendirilme sırası önemlidir. Yan etkileri olan bir fonksiyonu anlamak ve hata ayıklamak, bağlam ve olası geçmişi hakkında bilgi gerektirir. (ör.
foo(++i, i++)
ifadesi)
[düzenle] Örnek
'yan etki' ve 'değer hesaplama' kavramlarını aşağıdaki örnek ile daha iyi analiz edebiliriz:
#include <iostream> void foo(int a) { std::cout << a << std::endl; } int main() { int i = 5; foo(i++); // değer hesaplama: i'yi parametre olarak geçir // yan etki: daha sonra i'yi arttır std::cout << i << std::endl; // sonuç: i'yi yazdır }
Output:
5 6
Şimdi yukarıdaki foo fonksiyon-çağrı ifadesi üzerinden derleyicinin ifade 'değerlendirme' aşamalarına yakından bakalım:
foo(i++)
ifadesinin değerlendirilme süreci başlar-
i
değişkenini getirilir -
i
değişkenifoo
fonksiyonuna parametre olarak geçirilir (arttırılmadan önceki değeri fonksiyon içerisine kopyalanır) - fonksiyon parametresi için 'değer hesaplama' süreci sonlanır
i
değişkeninin değeri artırılır, böylece 'yan etki' oluşmuş olurfoo
fonksiyonunun gövdesi, değeri arttırılmadan önce fonksiyon gövdesi içerisine kopyalanmış olan (yerel) i
değişkeni üzerinden icra edilirfoo(i++)
ifadesinin değerlendirilme süreci sonlanır[düzenle] Sıralama
"önüne-sıralanma" aynı thread içinde gerçekleşen değerlendirmeler arasındaki öncelik ilişkisinde önce işlenen ifadeler için kullanılır. Sonra işlettirilecek olan ifadeler için ise "peşine-sıralanma" terimi kullanılır.
- Eğer A, B'nin önüne-sıralanmış ise, A'nın değerlendirilmesi B'nin ki başlamadan önce bitmiş olur.
- Eğer B, A'nın önüne-sıralanmış ise, B'nın değerlendirilmesi A'nin ki başlamadan önce bitmiş olur.
- Eğer A, B'nin önüne-sıralanmamış ve B'de A'nın önüne sıralanmamış ise o zaman iki ihtimal söz konusu olur:
- A ve B'nin değerlendirilmesi "sırasızdır": herhangi bir sıra ile değerlendirilebilirler. Hatta derleyici birinin değerlendirmesini yarıda kesip diğeri ile devam edebilir (program keyfi olarak farklı fonksiyon çağrılarını oluşturan işlemci yönergeleri araya sokulmuş gibi davranabilir).
- A ve B'nin değerlendirilmesi "belirsiz-sıralanmış"dır: herhangi bir sıra ile değerlendirilebilirler. Ancak derleyici birinin değerlendirmesini yarıda kesip diğeri ile devam etmez. Öte yandan gelecek sefer sıralama tam tersi şekilde değişebilir.
[düzenle] Kurallar
- hesaplanmayan ifadeler
- sabit ifade
- virgülle ayrılmış bileşenler dahil olmak üzere bütün bir ilklendirici
- geçici olmayan bir nesnenin hayat süresi sonunda üretilen yıkıcı fonksiyon çağrısı
- başka bir 'tam ifade'nin parçası olmayan bir ifade (bütün bir 'ifade deyimi', for/while döngü kontrol ifadesi, if/switch şartlı kontrol ifadesi veya return deyimindeki bir ifade vb.),
11'inci kuralın bir istisnası vardır: std::execution::par_unseq yürütme politikası altında yürütülen bir standart kütüphane algoritması tarafından yapılan fonksiyon çağrıları "sırasızdır" ve program keyfi olarak farklı fonksiyon çağrılarını oluşturan işlemci yönergeleri araya sokulmuş gibi davranabilir. | (C++17'den beri) |
13) Bir fonksiyondan geriye dönüş yapılırken, fonksiyon çağrısının değerlendirilmesi sonucu oluşan geriye dönüş değerinin kopyalanması işlemi, return deyimi'nin en son işleneniyle birlikte oluşan tüm geçici değişkenlerin ve return deyimini çevreleyen bloğun yerel değişkenlerinin yok edilmesinin önüne-sıralanır. |
(C++14'den beri) |
14) Bir fonksiyon-çağrısı ifadesinde, fonksiyona adını veren ifade, tüm parametre ifadelerinin (varsayılan parametre ifadeleri de dahil) önüne-sıralanır.
15) Bir fonksiyon çağrısında, her bir parametrenin fonksiyona geçirilmesi sırasında gerçekleşen 'değer hesaplamalar'ı ve 'yan etkiler', fonksiyonun diğer tüm parametrelerinin 'değer hesaplamalar'ı ve 'yan etkiler'i ile belirsiz-sıralanmıştır.
16) Aşırı yüklenen her operatör fonksiyonu, operatör notasyonu kullanarak çağrıldığında, aşırı yüklemiş olduğu yerleşik operatörün sıralama kurallarına uyar.
17) Bir dizi çağırma ifadesinde
E1[E2] , E1 ifadesinin her 'değer hesaplama' ve 'yan etki'si, E2 ifadesinin tüm 'değer hesaplama' ve 'yan etki'lerinin önüne-sıralanmıştır. 18) Bir üye-işaretçisi ifadesinde
E1.*E2 ya da E1->*E2 , E1 ifadesinin her 'değer hesaplama' ve 'yan etki'si, E2 ifadesinin tüm 'değer hesaplama' ve 'yan etki'lerinin önüne-sıralanmıştır. (E1 dinamik türü, E2'nin gösterdiği üyeyi içermiyorsa)19) Bir kaydırma operatör ifadesinde
E1<<E2 ve E1>>E2 , E1 ifadesinin her 'değer hesaplama' ve 'yan etki'si, E2 ifadesinin tüm 'değer hesaplama' ve 'yan etki'lerinin önüne-sıralanmıştır.20) Tüm basit atama ifadelerinde
E1=E2 ve her bileşik atama ifadesinde E1@=E2 , E2 ifadesinin her 'değer hesaplama' ve 'yan etki'si, E1 ifadesinin tüm 'değer hesaplama' ve 'yan etki'lerinin önüne-sıralanmıştır.21) Parantez içine alınmış bir ilklendiricideki, virgülle ayrılmış ifadeler listesindeki her bir ifade, bir fonksiyon çağrısı için olduğu gibi (aynı şekilde) değerlendirilir (belirsiz-sıralanmış, bkz. 15. madde)
|
(C++17'den beri) |
[düzenle] Tanımsız davranış
ilkel tür (scalar) kısaca: aritmetik türler (int, char, float vb.), işaretçiler T* (herhangi bir T türünü ifade eder), enum, üye-işaretçileri, nullptr_t türü
1) İlkel bir nesne üzerindeki bir yan etki, aynı ilkel nesne üzerindeki başka bir yan etki ile ilişkili olarak "belirsiz-sıralanmış" ise, davranış tanımsızdır.
i = ++i + 2; // tanımsız davranış C++11'e kadar
i = i++ + 2; // tanımsız davranış C++17'e kadar
f(i = -2, i = -2); // tanımsız davranış C++17'e kadar
f(++i, ++i); // tanımsız davranış C++17'e kadar, C++17'den sonra belirsizdir
i = ++i + i++; // tanımsız davranış
2) İlkel bir nesne üzerindeki bir yan etki, aynı ilkel nesne üzerindeki bir 'değer hesaplama' ile ilişkili olarak "belirsiz-sıralanmış" ise, davranış tanımsızdır.
cout << i << i++; // tanımsız davranış C++17'e kadar
a[i] = i++; // tanımsız davranış C++17'e kadar
n = ++i + i; // tanımsız davranış
[düzenle] Sequence point rules (C++11'e kadar)
Buradan ötesi eski C++'ı ilgilendirdiği için (C++11 öncesi) çevrilmemiştir. Siz de kalanını çevirerek katkıda bulunabilirsiniz.
[düzenle] Definitions
Evaluation of an expression might produce side effects, which are: accessing an object designated by a volatile lvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations.
A sequence point is a point in the execution sequence where all side effects from the previous evaluations in the sequence are complete, and no side effects of the subsequent evaluations started.
[düzenle] Rules
1) There is a sequence point at the end of each full expression (typically, at the semicolon).
2) When calling a function (whether or not the function is inline and whether or not function call syntax was used), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body.
3) There is a sequence point after the copying of a returned value of a function and before the execution of any expressions outside the function.
4) Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed (functions cannot be interleaved).
5) In the evaluation of each of the following four expressions, using the built-in (non-overloaded) operators, there is a sequence point after the evaluation of the expression a
.
a && b
a || b
a ? b : c
a , b
[düzenle] Undefined behavior
1) Between the previous and next sequence point a scalar object must have its stored value modified at most once by the evaluation of an expression, otherwise the behavior is undefined.
i = ++i + i++; // undefined behavior
i = i++ + 1; // undefined behavior
i = ++i + 1; // undefined behavior (well-defined in C++11)
++ ++i; // undefined behavior (well-defined in C++11)
f(++i, ++i); // undefined behavior
f(i = -1, i = -1); // undefined behavior
2) Between the previous and next sequence point, the prior value of a scalar object that is modified by the evaluation of the expression, must be accessed only to determine the value to be stored. If it is accessed in any other way, the behavior is undefined.
cout << i << i++; // undefined behavior
a[i] = i++; // undefined behavior
[düzenle] Defect reports
Şablon:dr list begin Şablon:dr list item Şablon:dr list end
[düzenle] References
Şablon:ref std c++11 Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std Şablon:ref std end
[düzenle] See also
- Operator precedence which defines how expressions are built from their source code representation.
C documentation for Order of evaluation
|