ProgramingTip

a = (a + b)-(b = a) 두 정수를 바꾸는 데 왜 나쁜 선택입니까?

bestdevel 2020. 12. 9. 21:34
반응형

a = (a + b)-(b = a) 두 정수를 바꾸는 데 왜 나쁜 선택입니까?


임시 변수 나 비트 연산자를 사용하지 않고 두 개의 정수를 바꾸는 코드를 발견했습니다.

int main(){

    int a=2,b=3;
    printf("a=%d,b=%d",a,b);
    a=(a+b)-(b=a);
    printf("\na=%d,b=%d",a,b);
    return 0;
}

그러나이 코드는 평가 순서를 결정하는 시퀀스 포인트 를 포함하지 a = (a+b) - (b=a);않기 때문에 스왑 문에서 정의되지 않은 동작을 가지고 있다고 생각합니다 .

내 질문은 : 이것이 두 정수를 교환하는 데 허용되는 솔루션입니까?


아니요. 이것은 허용되지 않습니다. 이 코드는 정의되지 않은 동작을 호출합니다 . 의 작업 이는 b이 정의되지 않았기 때문입니다 . 표현에서

a=(a+b)-(b=a);  

시퀀스 포인트 가 있기 때문에 그래서 b수정되거나 해당 값이 ( a+b) 언어에서 사용 가능 합니다. 표준 syas 확인 :

C11 : 6.5 저장 :

object-에, 대한 스칼라 부작용이 동일한 스칼라 object-에, 대한 다른 부작용이나 동일한 스칼라 object- 의 값을 사용하는 값 계산과 관련하여 순서 가 지정되지 않은 경우 동작이 정의되지 않습니다. .84) 1 .84) 1 .

읽기 C-FAQ - 3.8대답 순서 포인트와 정의되지 않은 동작의 더 자세한 설명.


1. 강조는 내 것입니다.


내 질문은-두 개의 정수를 교환하는 데 허용되는 솔루션입니까?

누구에게 허용 검증? 내가 받아 들일 수 있는지보고 내가 참여했던 코드 리뷰를 통과하지 못할 것입니다.

왜 a = (a + b)-(b = a) 두 정수를 바꾸는 데 잘못된 선택입니까?

다음과 같은 M

1) C에서는 실제로 그렇게 보장이 없습니다. 그것은 무엇이든 할 수 있습니다.

2) C #에서와 같이 실제로 두 개의 정수를 스왑하기 가정하십시오. (C #은 부작용이 오른쪽에서 오른쪽으로 발생 함을 보장합니다.) 코드의 의미가 완전히 명확하지 않으므로 코드는 여전히 허용되지 않습니다! 코드는 영리한 트릭이되어 있습니다. 읽고 이해해야하는 사람을 위해 코드를 작성하십시오.

3) 다시 작동 가정합니다. 이것은 단순한 실수이기 때문에 코드는 여전히 받아 들일 수 없습니다.

임시 변수를 사용하거나 비트 연산자를 사용하지 않고 두 정수를 바꾸는 코드를 우연히 발견했습니다.

그것은 거짓입니다. 이 트릭은 임시 변수를 사용하여 a+b. 변수는 사용자를 대신하여 컴파일러에 의해 생성되며 이름이 지정되지 않습니다. 목표가 일시적인 것을 제거하는 것이라면 더 나아지지 않고 더 나 빠지게 만듭니다! 그리고 애초에 일시적인 것을 제거하고 싶은 이유는 무엇입니까? 싸다!

4) 정수에서만 작동합니다. 정수 이외의 많은 것들이 스왑되어야합니다.

요컨대, 실제로 상황을 악화시키는 영리한 트릭을 생각해 내기보다는 정확한 코드를 작성하는 데 시간을 투자합니다.


최소한 두 가지 문제가 a=(a+b)-(b=a)있습니다.

자신에 대해 언급 한 것입니다. 따라서 모든 일이 일어날 수 있습니다. 예를 들어, 어떤 것이 먼저 평가되는지 보장하지 않습니다 : a+b또는 b=a. 컴파일러는 먼저 할당에 대한 코드를 생성하거나 완전히 다른 작업을 수행 할 수 있습니다.

또 다른 문제는 부호있는 산술의 오버플로가 정의되지 않은 동작이라는 사실입니다. 만약 a+b결과의 보장은 없습니다 오버 플로우; 예외도있을 수 있습니다.


정의되지 않은 동작 및 임시 변수를 사용하는 간단한 코드를 작성하면 컴파일러가 값을 추적하고 생성 된 코드에서 실제로 교환하지 않고 나중에 일부에서 교환 한 값을 사용할 수 있습니다. 케이스. 귀하의 코드는 그렇게 할 수 없습니다. 컴파일러는 일반적으로 마이크로 최적화에서 당신보다 낫습니다.

코드가 더 느리고 이해하기 어렵고 수없는 정의되지 않은 동작 일 가능성이 있습니다.


gcc를 사용 -Wall하고 컴파일러가 이미 경고하는 경우

ac : 3 : 26 : 경고 : 'b'에 대한 작업이 발생하지 않을 수 있습니다. [-W 시퀀스 포인트]

성능 구조의 사용 여부는 성능입니다. 당신이 볼 때

void swap1(int *a, int *b)
{
    *a = (*a + *b) - (*b = *a);
}

void swap2(int *a, int *b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

어셈블리 코드 검토

swap1:
.LFB0:
    .cfi_startproc
    movl    (%rdi), %edx
    movl    (%rsi), %eax
    movl    %edx, (%rsi)
    movl    %eax, (%rdi)
    ret
    .cfi_endproc

swap2:
.LFB1:
    .cfi_startproc
    movl    (%rdi), %eax
    movl    (%rsi), %edx
    movl    %edx, (%rdi)
    movl    %eax, (%rsi)
    ret
    .cfi_endproc

코드를 난독 화해도 상관 없습니다.


기본적으로 동일하지만 move고려 하는 C ++ (g ++) 코드 살펴보기

#include <algorithm>

void swap3(int *a, int *b)
{
    std::swap(*a, *b);
}

동일한 어셈블리 출력 제공

_Z5swap3PiS_:
.LFB417:
    .cfi_startproc
    movl    (%rdi), %eax
    movl    (%rsi), %edx
    movl    %edx, (%rdi)
    movl    %eax, (%rsi)
    ret
    .cfi_endproc

gcc의 경고를 고려하고 기술적 이득이 없다고 생각하면 표준 기술을 고수한다고 말하고 싶습니다. 이것이 병목 현상이되는 경우에도이 작은 코드 조각을 개선하거나 방지하는 방법을 조사 할 수 있습니다.


진술 :

a=(a+b)-(b=a);

정의되지 않은 동작을 호출합니다. 인용 된 단락의 두 번째는 위반됩니다.

(C99, 6.5p2) "이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에서 객체는 표현식 평가에 의해 최대 한 번만 수정 된 저장된 값을 가져야합니다. 또한 이전 값은 저장 될 값을 결정하기 위해 읽기 전용이어야합니다. "


2010 년 에 똑같은 예와 함께 질문이 게시되었습니다 .

a = (a+b) - (b=a);

Steve Jessop 은 이에 대해 경고합니다.

그런데 그 코드의 동작은 정의되지 않았습니다. a와 b는 모두 중간 시퀀스 포인트없이 읽고 씁니다. 우선, 컴파일러는 a + b를 평가하기 전에 b = a를 평가할 권한이 있습니다.

다음은 2012 년에 게시 된 질문의 설명입니다 . 샘플은 괄호가 없기 때문에 정확히 동일하지는 않지만 그럼에도 불구하고 대답은 여전히 ​​관련이 있습니다.

C ++에서 산술 표현식의 하위 표현식에는 시간 순서가 없습니다.

a = x + y;

x가 먼저 평가됩니까, 아니면 y입니까? 컴파일러는 둘 중 하나를 선택하거나 완전히 다른 것을 선택할 수 있습니다. 평가 순서는 연산자 우선 순위와 같지 않습니다. 연산자 우선 순위는 엄격하게 정의되고 평가 순서는 프로그램에 시퀀스 포인트가있는 단위로만 정의됩니다.

실제로 일부 아키텍처에서는 x와 y를 동시에 평가하는 코드를 내보낼 수 있습니다 (예 : VLIW 아키텍처).

이제 N1570의 C11 표준 견적 :

부록 J.1 / 1

다음과 같은 경우 지정되지 않은 동작입니다.

- 표현식을 평가하는 순서와 부작용이 함수 호출에 대해 지정되는 경우를 제외하고, 일어날 순서 (), &&, ||, ? :, 및 콤마 연산자 (6.5).

— 할당 연산자의 피연산자가 평가되는 순서 (6.5.16).

부록 J.2 / 1

다음과 같은 경우 정의되지 않은 동작입니다.

— 스칼라 객체에 대한 부작용은 동일한 스칼라 객체에 대한 다른 부작용이나 동일한 스칼라 객체 (6.5)의 값을 사용하는 값 ​​계산과 관련하여 순서가 지정되지 않습니다.

6.5 / 1

표현식은 값의 계산을 지정하거나 객체 또는 함수를 지정하거나 부작용을 생성하거나 이들의 조합을 수행하는 일련의 연산자 및 피연산자입니다. 연산자의 피연산자의 값 계산은 연산자 결과의 값 계산 전에 순서가 지정됩니다.

6.5 / 2

스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 다른 부작용이나 동일한 스칼라 객체의 값을 사용하는 값 ​​계산과 관련하여 순서가 지정되지 않은 경우 동작이 정의되지 않습니다. 식의 하위 식에 허용 가능한 순서가 여러 개있는 경우 순서에 따라 순서가 지정되지 않은 부작용이 발생하면 동작이 정의되지 않습니다 .84)

6.5 / 3

연산자와 피연산자의 그룹화는 구문으로 표시됩니다 .85) 나중에 지정된 경우를 제외하고 하위 표현식의 부작용 및 값 계산은 순서가 없습니다 .86)

정의되지 않은 동작에 의존해서는 안됩니다.

일부 대안 : C ++에서는 다음을 사용할 수 있습니다.

  std::swap(a, b);

XOR- 스왑 :

  a = a^b;
  b = a^b;
  a = a^b;

문제는 C ++ 표준에 따라

언급 된 경우를 제외하고 개별 연산자의 피연산자 및 개별 식의 하위 식에 대한 평가는 순서가 지정되지 않습니다.

그래서이 표현은

a=(a+b)-(b=a);

정의되지 않은 동작이 있습니다.


XOR 스왑 알고리즘 을 사용하여 오버플로 문제를 방지하고 여전히 한 줄을 유지할 수 있습니다 .

그러나 c++태그 가 있으므로 std::swap(a, b)더 쉽게 읽을 수 있도록 간단한 것을 선호합니다 .


다른 사용자가 호출 한 문제 외에도 이러한 유형의 스왑에는 도메인 이라는 또 다른 문제 가 있습니다 .

어떤 경우에하는 것은 a+b그것의 한계를 넘어? 에서 0까지의 숫자로 작업한다고 가정 해 보겠습니다 100. 80+ 60범위를 벗어날 것입니다.

참고 URL : https://stackoverflow.com/questions/20800684/why-is-a-ab-baa-bad-choice-for-swapping-two-integers

반응형