(A + B + C) ≠ (A + C + B) 및 컴파일러 재주문
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_x
그리고u32_y
64비트 미서명 값.이것은 깁스를 추가하는 것과 같다.(실제 최적화는 C레벨에서 이루어지는 것이 아니라, 우리가 이해하는 표기법이기 때문에 나는 C 표기법을 사용한다.)
컴파일러는 마치 규칙처럼 에 따라 재주문만 허용된다.즉, 재주문 시 항상 지정된 주문과 동일한 결과가 나온다면 허용된다.그렇지 않으면(당신의 예와 같이), 그렇지 않다.
예를 들어, 다음 식을 지정하면
i32big1 - i32big2 + i32small
크지만 유사한 것으로 알려진 두 값을 빼서 다른 작은 값(과부하를 피함)을 더하기 위해 신중하게 구성된 컴파일러는 다음과 같이 순서를 변경할 수 있다.
(i32small - i32big2) + i32big1
그리고 목표 플랫폼이 문제를 예방하기 위해 랩 라운드와 함께 2개의 산수를 사용하고 있다는 사실에 의존한다.(이러한 재배열은 컴파일러가 레지스터에 눌려져 있고, 우연히 다음과 같은 경우에 합리적일 수 있다.i32small
이미 등록되어 있다.
C, C++ 및 Objective-C에 "있는 것처럼" 규칙이 있다.컴파일러는 순응하는 프로그램이 차이를 구별할 수 없는 한 자신이 원하는 것은 무엇이든 할 수 있다.
이러한 언어에서 + b + c는 (a + b) + c와 동일하게 정의된다. 만약 이것과 + (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_y
0이 되다.
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
bits). 이 비트)의 이 되었다. 정수 프로모션 발생u32_x + u32_y
64비트 수학에서 추가로 발생한다.질서는 관계없다.
UB(When))이다.unsigned == 33
비트(bits). 서명된 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;
참조URL: https://stackoverflow.com/questions/38563707/a-b-c-%e2%89%a0-a-c-b-and-compiler-reordering
'Programing' 카테고리의 다른 글
IntelliJ IDEA 13은 1.7로 설정되었음에도 불구하고 Java 1.5를 사용한다. (0) | 2022.05.09 |
---|---|
Java 8 인터페이스 메서드에서 "최종"이 허용되지 않는 이유는? (0) | 2022.05.09 |
1D 또는 2D 어레이, 어떤 것이 더 빠른가? (0) | 2022.05.08 |
정의되지 않은 VUEX의 '커밋' 속성을 읽을 수 없음 (0) | 2022.05.08 |
언제 부울의 null 값을 사용해야 하는가? (0) | 2022.05.08 |