(A + B + C) ≠ (A + C + B) ve derleyici yeniden sıralama


108

İki 32 bitlik tamsayı eklemek, tamsayı taşmasına neden olabilir:

uint64_t u64_z = u32_x + u32_y;

32 bitlik tam sayılardan biri önce dönüştürülür veya 64 bitlik bir tam sayıya eklenirse bu taşma önlenebilir.

uint64_t u64_z = u32_x + u64_a + u32_y;

Ancak, derleyici eklemeyi yeniden düzenlemeye karar verirse:

uint64_t u64_z = u32_x + u32_y + u64_a;

tamsayı taşması yine de olabilir.

Derleyicilerin böyle bir yeniden sıralama yapmasına izin veriliyor mu yoksa sonuç tutarsızlığını fark edip ifade sırasını olduğu gibi tutmaları için onlara güvenebilir miyiz?


15
Gerçekte tamsayı taşması göstermezsiniz çünkü eklenmiş uint32_tdeğerler olarak görünüyorsunuz - ki bunlar taşmaz, sarılırlar. Bunlar farklı davranışlar değil.
Martin Bonner

5
C ++ standartlarının 1.9 bölümüne bakın, doğrudan sorunuza cevap verir (neredeyse tamamen sizinkiyle aynı olan bir örnek bile vardır).
Holt

3
@Tal: Diğerlerinin daha önce de belirttiği gibi: tamsayı taşması yok. İmzalanmamış, sarmak için tanımlanır, imzalanması tanımsız bir davranıştır, bu nedenle nazal arka plan olayları da dahil olmak üzere herhangi bir uygulama işe yarar.
bu site için çok dürüst

5
@Tal: Saçma! Daha önce yazdığım gibi: standart çok açık ve doyurucu değil, sarma gerektiriyor (bu standart olarak UB olduğu için imzayla mümkün olabilir.
bu site için çok dürüst

15
@rustyx: İster çağrı onu sarma veya, önemli olan o kalıntıları taşan ((uint32_t)-1 + (uint32_t)1) + (uint64_t)0sonuçlanır 0, oysa (uint32_t)-1 + ((uint32_t)1 + (uint64_t)0)sonuçları 0x100000000ve bu iki değerin eşit değildir. Bu nedenle, derleyicinin bu dönüşümü uygulayıp uygulayamayacağı önemlidir. Ama evet, standart sadece işaretli tamsayılar için "taşma" kelimesini kullanır, işaretsiz değil.
Steve Jessop

Yanıtlar:


84

İyileştirici böyle bir yeniden sıralama yaparsa, yine de C spesifikasyonuna bağlıdır, bu nedenle böyle bir yeniden sıralama şöyle olur:

uint64_t u64_z = (uint64_t)u32_x + (uint64_t)u32_y + u64_a;

Gerekçe:

İle başlıyoruz

uint64_t u64_z = u32_x + u64_a + u32_y;

Ekleme soldan sağa gerçekleştirilir.

Tamsayı yükseltme kuralları, orijinal ifadedeki ilk eklemede u32_xyükseltileceğini belirtir uint64_t. İkinci ilavede, u32_yayrıcauint64_t .

Yani, C spesifikasyonu ile uyumlu olması için, herhangi optimizasyoncusu tanıtmalıdır u32_xve u32_y64'e imzasız değerleri ısırdı. Bu, bir oyuncu eklemeye eşdeğerdir. (Gerçek optimizasyon C düzeyinde yapılmaz, ancak C notasyonunu kullanıyorum çünkü bu, anladığımız bir gösterimdir.)


Sol-çağrışımlı değil (u32_x + u32_t) + u64_ami?
Yararsız

12
@Useless: Klas her şeyi 64 bit'e çevirdi. Şimdi sıra hiç bir fark yaratmıyor. Derleyicinin ilişkilendirilebilirliği takip etmesi gerekmez, sadece yaptığı gibi aynı sonucu üretmesi gerekir.
gnasher729

2
Görünüşe göre OP'nin kodunun bu şekilde değerlendirileceğini öne sürüyor, ki bu doğru değil.
Yararsız

@Klas - durumun neden böyle olduğunu ve kod örneğinize tam olarak nasıl ulaştığınızı açıklamaya özen gösterin.
rustyx

1
@rustyx Bir açıklamaya ihtiyacı vardı. Bir tane eklemem için beni zorladığınız için teşekkürler.
Klas Lindbäck

28

Bir derleyicinin yalnızca sanki kuralına göre yeniden sipariş vermesine izin verilir . Diğer bir deyişle, yeniden sıralama her zaman belirtilen sıralamayla aynı sonucu verecekse, buna izin verilir. Aksi takdirde (örneğinizdeki gibi), hayır.

Örneğin, aşağıdaki ifade verildiğinde

i32big1 - i32big2 + i32small

Büyük ancak benzer olduğu bilinen iki değeri çıkarmak ve ancak o zaman diğer küçük değeri eklemek için (böylece herhangi bir taşmayı önleyerek) dikkatlice yapılandırılmış olan derleyici, aşağıdakileri yeniden sıralamayı seçebilir:

(i32small - i32big2) + i32big1

ve hedef platformun sorunları önlemek için sarmalı iki tamamlayıcı aritmetik kullanması gerçeğine güvenin. (Böyle bir yeniden sıralama, derleyiciye kayıtlar için basılırsa ve i32smallzaten bir kayıtta varsa mantıklı olabilir ).


OP'nin örneği, işaretsiz türleri kullanır. i32big1 - i32big2 + i32smallişaretli türleri ima eder. Ek endişeler devreye giriyor.
chux

@chux Kesinlikle. Yapmaya çalıştığım nokta (i32small-i32big2) + i32big1, yazamasam da (çünkü UB'ye neden olabilir), derleyicinin onu etkili bir şekilde yeniden düzenleyebilmesidir, çünkü derleyici davranışın doğru olacağından emin olabilir.
Martin Bonner

3
@chux: UB gibi ek endişeler devreye girmiyor, çünkü sanki kuralı altında yeniden sıralama yapan bir derleyiciden bahsediyoruz. Belirli bir derleyici, kendi taşma davranışını bilmenin avantajını kullanabilir.
MSalters

16

C, C ++ ve Objective-C'de "sanki" kuralı vardır: Hiçbir uyumlu program farkı anlayamadığı sürece derleyici istediği her şeyi yapabilir.

Bu dillerde a + b + c, (a + b) + c ile aynı şekilde tanımlanır. Bunun ile örneğin a + (b + c) arasındaki farkı anlayabilirseniz, derleyici sırayı değiştiremez. Farkı anlayamazsanız, derleyici sırayı değiştirmekte özgürdür, ancak sorun değil, çünkü farkı anlayamazsınız.

Örneğinizde, b = 64 bit, a ve c 32 bit ile, derleyicinin (b + a) + c veya hatta (b + c) + a'yı değerlendirmesine izin verilir, çünkü farkı anlayamazsınız, ancak (a + c) + b değil çünkü farkı anlayabilirsiniz.

Diğer bir deyişle, derleyicinin, kodunuzun olması gerekenden farklı davranmasını sağlayan hiçbir şey yapmasına izin verilmez. Yaratabileceği düşünüyorum, ya da bunu üretmek gerektiğini düşünüyorum o kodu üretmek için gerekli değildir, ancak kod olacak size gerektiği tam olarak sonuçlar verir.


Ancak büyük bir uyarı ile; derleyici tanımsız bir davranışı varsaymakta özgürdür (bu durumda taşma). Bu, bir taşma kontrolünün nasıl if (a + 1 < a)optimize edilebileceğine benzer .
csiz

7
@csiz ... işaretli değişkenlerde. İmzalanmamış değişkenler iyi tanımlanmış taşma anlamlarına sahiptir (etrafını sarar).
Gavin S. Yancey

7

Standartlardan alıntı yapmak :

[Not: Operatörler, yalnızca operatörlerin gerçekten ilişkisel veya değişmeli olduğu durumlarda olağan matematik kurallarına göre yeniden gruplandırılabilir.7 Örneğin, aşağıdaki parçada int a, b;

/∗ ... ∗/
a = a + 32760 + b + 5;

ifade ifadesi tam olarak aynı şekilde davranır

a = (((a + 32760) + b) + 5);

bu operatörlerin ilişkilendirilebilirliği ve önceliği nedeniyle. Böylece, toplamın sonucu (a + 32760) daha sonra b'ye eklenir ve bu sonuç daha sonra 5'e eklenir ve bu da a'ya atanan değerle sonuçlanır. Taşmaların bir istisna oluşturduğu ve int ile temsil edilebilen değer aralığının [-32768, + 32767] olduğu bir makinede, uygulama bu ifadeyi şu şekilde yeniden yazamaz:

a = ((a + b) + 32765);

çünkü a ve b için değerler sırasıyla -32754 ve -15 olsaydı, a + b toplamı bir istisna üretirken orijinal ifade olmazdı; ne de ifade şu şekilde yeniden yazılamaz:

a = ((a + 32765) + b);

veya

a = (a + (b + 32765));

a ve b için değerler sırasıyla 4 ve -8 veya -17 ve 12 olabileceğinden, ancak taşmaların bir istisna oluşturmadığı ve taşma sonuçlarının tersine çevrilebildiği bir makinede, yukarıdaki ifade ifadesi yukarıdaki yollardan herhangi biri uygulama tarafından yeniden yazılmalıdır çünkü aynı sonuç ortaya çıkacaktır. - son not]


4

Derleyicilerin böyle bir yeniden sıralama yapmasına izin veriliyor mu yoksa sonuç tutarsızlığını fark edip ifade sırasını olduğu gibi tutmaları için onlara güvenebilir miyiz?

Derleyici yalnızca aynı sonucu verirse yeniden sıralayabilir - burada, gözlemlediğiniz gibi, vermez.


İsterseniz, tüm argümanları std::common_typeeklemeden önce destekleyen bir işlev şablonu yazmak mümkündür - bu güvenli olur ve argüman sırasına veya manuel atamaya bağlı değildir, ancak oldukça kullanışsızdır.


Açık atamanın kullanılması gerektiğini biliyorum, ancak bu tür bir çevrim yanlışlıkla ihmal edildiğinde derleyicinin davranışını bilmek istiyorum.
Tal

1
Söylediğim gibi, açık bir çevrim olmadan: ilk olarak sol toplama, integral yükseltme olmadan gerçekleştirilir ve bu nedenle sarmaya tabidir. Bu eklemenin sonucu , muhtemelen sarılmış, daha sonrauint64_t en sağdaki değere eklenmek üzere yükseltilir .
Yararsız

Sanki kuralı hakkındaki açıklamanız tamamen yanlış. Örneğin C dili, soyut bir makinede hangi işlemlerin olması gerektiğini belirtir. "Sanki" kuralı, hiç kimse farkı söyleyemediği sürece, istediği her şeyi kesinlikle yapmasına izin verir.
gnasher729

Bu, derleyicinin, sonuç gösterilen sol-ilişkilendirilebilirlik ve aritmetik dönüştürme kuralları tarafından belirlenenle aynı olduğu sürece istediği her şeyi yapabileceği anlamına gelir.
Yararsız

1

Bit genişliğine bağlıdır unsigned/int.

Aşağıdaki 2 aynı değildir ( unsigned <= 32bit olduğunda ). u32_x + u32_y0 olur.

u64_a = 0; u32_x = 1; u32_y = 0xFFFFFFFF;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + u32_y + u64_a;  // u32_x + u32_y carry does not add to sum.

Aynıdırlar ( unsigned >= 34bit olduğunda ). Tamsayı promosyonlarının neden olduğu u32_x + u32_y eklemenin 64 bit matematikte gerçekleşmesine . Düzen alakasız.

UB'dir ( unsigned == 33bit olduğunda ). Tamsayı yükseltmeleri, eklemenin işaretli 33 bit matematikte gerçekleşmesine neden oldu ve işaretli taşma UB'dir.

Derleyicilerin böyle bir yeniden sıralama yapmasına izin verilir mi?

(32 bit matematik): Yeniden sipariş evet, ama aynı sonuçlar çok değil, gerçekleşmesi gerekir o yeniden sipariş OP önermektedir. Aşağıda aynı

// Same
u32_x + u64_a + u32_y;
u64_a + u32_x + u32_y;
u32_x + (uint64_t) u32_y + u64_a;
...

// Same as each other below, but not the same as the 3 above.
uint64_t u64_z = u32_x + u32_y + u64_a;
uint64_t u64_z = u64_a + (u32_x + u32_y);

... sonuç tutarsızlığını fark etmeleri ve ifade sırasını olduğu gibi tutmaları için onlara güvenebilir miyiz?

Güvenin evet, ancak OP'nin kodlama hedefi kristal netliğinde değil. Meli u32_x + u32_ykatkıda taşımak? OP bu katkıyı istiyorsa, kod

uint64_t u64_z = u64_a + u32_x + u32_y;
uint64_t u64_z = u32_x + u64_a + u32_y;
uint64_t u64_z = u32_x + (u32_y + u64_a);

Ama değil

uint64_t u64_z = u32_x + u32_y + u64_a;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.