programing

= (a+b) - (b=a)가 두 정수를 스왑하는 데 좋지 않은 이유는 무엇입니까?

shortcode 2022. 8. 12. 21:13
반응형

= (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);평가 순서를 결정하는 시퀀스 포인트가 포함되어 있지 않기 때문입니다.

질문입니다.이것이 2개의 정수를 교환할 수 있는 솔루션입니까?

안 돼, 이건 용납할 수 없어이 코드는 정의되지 않은 동작을 호출합니다.이것은, 다음의 조작에 의한 것입니다.b정의되어 있지 않습니다.(에)

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

않다b됩니다(예: 변경됨).a+b시퀀스 포인트가 없기 때문입니다.
§ syas:

C11: 6.5 식:

스칼라 객체에 대한 부작용이 같은 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용한 계산과 비교하여 시퀀스되지 않은 경우 동작은 정의되지 않습니다.표현의 서브 표현의 허용 순서가 여러 개 있는 경우, 순서하나에서 이러한 비순서 부작용이 발생하면 동작은 정의되지 않는다.84).1

시퀀스 포인트와 정의되지 않은 동작에 대한 자세한 설명은 C-faq- 3.8 및 이 답변을 참조하십시오.


1. 강조는 나의 것이다.

질문입니다.이것이 2개의 정수를 교환할 수 있는 솔루션입니까?

누구에게 받아들여질까?만약 당신이 나에게 그것이 받아들여질 수 있는지 묻는다면, 그것은 내가 참여했던 어떤 코드 리뷰도 통과하지 못할 것이다, 나를 믿어라.

a=(a+b)-(b=a)가 두 정수를 스왑하는 데 좋지 않은 선택인 이유는 무엇입니까?

다음과 같은 이유:

1) 아시다시피, C에서는 실제로 그렇게 한다는 보장은 없습니다.뭐든 할 수 있어

2) C#에서와 같이 실제로 2개의 정수를 교환한다고 가정합니다.(C#은 부작용이 왼쪽에서 오른쪽으로 발생함을 보증합니다.)그 암호는 그 의미가 전혀 분명하지 않기 때문에 여전히 받아들여지지 않을 것이다!암호는 교묘한 속임수일 수 없어읽고 이해해야 하는 사람을 위해 코드를 작성합니다.

3) 다시, 동작한다고 가정합니다.이것은 명백한 거짓이기 때문에 이 코드는 여전히 허용되지 않습니다.

Temporary 변수나 비트 단위 연산자를 사용하지 않고 두 정수를 교환하기 위해 이 코드를 발견했습니다.

그것은 순전히 거짓이다.은 임시 합니다.a+b변수는 사용자 대신 컴파일러에 의해 생성되며 이름은 지정되지 않았지만 여기에 있습니다.일시적 장치를 제거하는 것이 목표라면, 더 나은 방법이 아니라 더 나쁜 방법이 될 것입니다!초에 왜왜 시시 시? ???!!!

4) 이는 정수에만 적용됩니다.정수 말고도 많은 것들을 바꿔야 합니다.

간단히 말해서, 상황을 악화시키는 영리한 속임수를 생각해내려고 하기보다는 분명히 맞는 코드를 쓰는 데 시간을 투자하세요.

.a=(a+b)-(b=a).

당신이 말한 것 중 하나는 시퀀스 포인트가 부족하다는 것은 동작이 정의되지 않았다는 것을 의미합니다.이치노릇을 하다를 들어, 어떤 이 먼저 할 수 없습니다. 즉, 어떤 것이 먼저 평가되는지, 즉, 어떤 것이 먼저 평가되는지는 알 수 없습니다.a+b ★★★★★★★★★★★★★★★★★」b=a컴파일러는 먼저 할당용 코드를 생성하거나 완전히 다른 작업을 수행할 수 있습니다.

또 다른 문제는 서명된 산술의 오버플로가 정의되지 않은 동작이라는 사실이다. ifa+b오버플로우는 결과를 보장하지 않으며 예외가 발생할 수도 있습니다.

정의되지 않은 동작이나 스타일에 관한 다른 답변과는 별도로 일시적인 변수만을 사용하는 단순한 코드를 쓰면 컴파일러는 값을 추적하고 생성된 코드로 실제로 스왑하지 않고 나중에 스왑된 값을 사용할 수 있습니다.당신의 코드로는 그럴 수 없어요.컴파일러는 일반적으로 마이크로 최적화에 있어서는 사용자보다 우수합니다.

따라서 코드가 느리고 이해하기 어려우며 정의되지 않은 동작도 신뢰할 수 없습니다.

및 gcc를 사용하는 -Wall.

a.c:3:26: 경고: 'b'에 대한 작업이 정의되지 않았을 수 있습니다 [-Wsequence-point]

이러한 구성을 사용할지 여부는 성능 측면에서도 논의될 수 있습니다.보면

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

코드를 난독화해도 아무런 이점이 없습니다.


C++(g++) 코드를 보면 기본적으로는 같지만move고려해서

#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로로 합니다.10080+ 60범위를 벗어납니다.

언급URL : https://stackoverflow.com/questions/20800684/why-is-a-ab-b-a-a-bad-choice-for-swapping-two-integers

반응형