(A + B + C) ≠ (A + C + B) 및 컴파일러 재정렬 64 비트 정수에 추가되는 경우

두 개의 32 비트 정수를 추가하면 정수 오버 플로우가 발생할 수 있습니다.

uint64_t u64_z = u32_x + u32_y;

32 비트 정수 중 하나가 먼저 캐스트되거나 64 비트 정수에 추가되는 경우 이러한 오버플로를 피할 수 있습니다.

uint64_t u64_z = u32_x + u64_a + u32_y;

그러나 컴파일러가 추가 순서를 변경하기로 결정한 경우 :

uint64_t u64_z = u32_x + u32_y + u64_a;

정수 오버플로가 계속 발생할 수 있습니다.

컴파일러가 이러한 재정렬을 수행 할 수 있습니까? 아니면 결과 불일치를 인식하고 표현식 순서를 그대로 유지할 수 있다고 믿을 수 있습니까?



답변

옵티마이 저가 이러한 재정렬을 수행하면 여전히 C 사양에 바인딩되어 있으므로 이러한 재정렬은 다음과 같습니다.

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

이론적 해석:

우리는

uint64_t u64_z = u32_x + u64_a + u32_y;

추가는 왼쪽에서 오른쪽으로 수행됩니다.

정수 승격 규칙은 원래 표현식의 첫 번째 추가에서 u32_x로 승격되도록 지정 uint64_t합니다. 두 번째 추가에서u32_y 도 승격됩니다 uint64_t.

그래서, C 규격을 준수하기 위해, 어떤 최적화를 촉진해야한다 u32_xu32_y64 부호 값을 비트. 이것은 캐스트를 추가하는 것과 같습니다. (실제 최적화는 C 수준에서 수행되지 않지만 우리가 이해하는 표기법이므로 C 표기법을 사용합니다.)


답변

컴파일러는 as if 규칙 아래에서만 순서를 변경할 수 있습니다. 즉, 재정렬이 항상 지정된 주문과 동일한 결과를 제공하는 경우 허용됩니다. 그렇지 않으면 (귀하의 예에서와 같이) 아닙니다.

예를 들어 다음 표현식이 주어지면

i32big1 - i32big2 + i32small

크지 만 유사한 것으로 알려진 두 값을 뺀 다음 다른 작은 값 을 더하기 위해 신중하게 구성되어 있습니다 (따라서 오버플로 방지), 컴파일러는 다음과 같이 재정렬하도록 선택할 수 있습니다.

(i32small - i32big2) + i32big1

대상 플랫폼이 문제를 방지하기 위해 랩-라운드가있는 2- 보체 산술을 사용하고 있다는 사실에 의존합니다. (컴파일러가 레지스터에 대해 눌려져 i32small있고 레지스터에 이미 있는 경우 이러한 재정렬이 적절할 수 있습니다 .)


답변

C, C ++ 및 Objective-C에는 “as if”규칙이 있습니다. 컴파일러는 적합한 프로그램이 차이를 알 수없는 한 원하는대로 수행 할 수 있습니다.

이러한 언어에서 a + b + c는 (a + b) + c와 동일하게 정의됩니다. 이것과 예를 들어 a + (b + c) 사이의 차이점을 알 수 있다면 컴파일러는 순서를 변경할 수 없습니다. 차이를 알 수 없다면 컴파일러는 순서를 자유롭게 변경할 수 있지만 차이를 알 수 없기 때문에 괜찮습니다.

귀하의 예에서 b = 64 비트, a 및 c 32 비트를 사용하면 컴파일러가 (b + a) + c 또는 (b + c) + a를 평가할 수 있습니다. 왜냐하면 차이점을 말할 수 없기 때문에 차이를 알 수 있기 때문에 (a + c) + b가 아닙니다.

즉, 컴파일러는 코드가해야하는 것과 다르게 동작하도록 만드는 작업을 수행 할 수 없습니다. 생성 할 것이라고 생각하거나 생성해야한다고 생각하는 코드를 생성 할 필요는 없지만 코드 정확한 결과를 제공합니다.


답변

표준 에서 인용 :

[참고 : 연산자는 실제로 연관성 또는 교환 성인 경우에만 일반적인 수학적 규칙에 따라 다시 그룹화 할 수 있습니다 .7 예를 들어 다음 단편에서 int a, b;

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

표현식 문은 다음과 정확히 동일하게 작동합니다.

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

이러한 연산자의 연관성과 우선 순위 때문입니다. 따라서 합계 (a + 32760)의 결과가 다음에 b에 더 해지고 그 결과가 5에 더해져 값이 a에 할당됩니다. 오버플로로 인해 예외가 발생하고 int로 표현할 수있는 값의 범위가 [-32768, + 32767] 인 시스템에서 구현은이 식을 다음과 같이 다시 작성할 수 없습니다.

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

a와 b의 값이 각각 -32754와 -15이면 합계 a + b는 예외를 생성하지만 원래 표현식은 그렇지 않기 때문입니다. 식을 다음과 같이 다시 작성할 수도 없습니다.

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

또는

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

a와 b의 값은 각각 4와 -8 또는 -17과 12 일 수 있기 때문입니다. 그러나 오버플로가 예외를 생성하지 않고 오버플로 결과를 되돌릴 수있는 시스템에서는 위의 식 문을 사용할 수 있습니다. 동일한 결과가 발생하기 때문에 위의 방법 중 하나로 구현에 의해 다시 작성됩니다. — 끝 참고]


답변

컴파일러가 이러한 재정렬을 수행 할 수 있습니까? 아니면 결과 불일치를 인식하고 표현식 순서를 그대로 유지할 수 있다고 믿을 수 있습니까?

컴파일러는 동일한 결과를 제공하는 경우에만 재정렬 할 수 있습니다. 여기에서는 관찰했듯이 그렇지 않습니다.


원하는 경우 std::common_type추가 하기 전에 모든 인수를 승격하는 함수 템플릿을 작성할 수 있습니다. 이것은 안전하고 인수 순서 나 수동 캐스팅에 의존하지 않지만 꽤 투박합니다.


답변

의 비트 폭에 따라 다릅니다 unsigned/int.

아래 2는 동일하지 않습니다 ( unsigned <= 32비트 일 때 ). u32_x + u32_y0이됩니다.

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.

그들은 동일합니다 ( unsigned >= 34비트 때 ). 정수 프로모션 발생 u32_x + u32_y 64 비트 수학에서 덧셈이 발생했습니다. 순서는 무관합니다.

UB ( unsigned == 33비트 때 )입니다. 정수 승격으로 인해 부호있는 33 비트 수학에서 덧셈이 발생했으며 부호있는 오버플로는 UB입니다.

컴파일러가 이러한 재정렬을 할 수 있습니까 …?

(32 비트 수학) : 재주문 예, 그러나 동일한 결과가 발생해야하므로 재주문 OP가 제안 하는 것은 아닙니다 . 아래는 동일합니다

// 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);

… 그들이 결과 불일치를 인식하고 표현 순서를 그대로 유지할 수 있다고 믿을 수 있습니까?

예를 믿으십시오. 그러나 OP의 코딩 목표는 명확하지 않습니다. u32_x + u32_y캐리가 기여 해야합니까 ? OP가 그 기여를 원하면 코드는

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);

하지만

uint64_t u64_z = u32_x + u32_y + u64_a;