f (i = -1, i = -1) 동작이 정의되지 않은 이유는 무엇입니까?


267

나는 평가 위반 순서에 대해 읽고 있었고 , 그들은 나를 당황스럽게하는 예를 제시합니다.

1) 스칼라 객체의 부작용이 동일한 스칼라 객체의 다른 부작용에 비해 순서가 맞지 않으면 동작이 정의되지 않습니다.

// snip
f(i = -1, i = -1); // undefined behavior

이러한 맥락에서, iA는 스칼라 객체 명백하게 수단

산술 유형 (3.9.1), 열거 유형, 포인터 유형, 멤버 유형에 대한 포인터 (3.9.2), std :: nullptr_t 및 이러한 유형의 cv 규정 버전 (3.9.3)을 통칭하여 스칼라 유형이라고합니다.

이 경우 진술이 어떻게 모호한 지 알 수 없습니다. 첫 번째 또는 두 번째 인수가 먼저 평가되는지 여부에 관계없이로 i끝나고 -1두 인수도 모두 같습니다 -1.

누군가가 명확히 할 수 있습니까?


최신 정보

나는 모든 토론에 정말 감사합니다. 지금까지 나는 @harmic의 대답을 좋아 합니다. 왜냐하면이 문장을 정의하는 데 따르는 함정과 복잡성이 언뜻보기에 눈에 띄게 보이지만 복잡합니다. @ acheong87 은 참조를 사용할 때 발생하는 몇 가지 문제를 지적하지만이 질문의 순서가없는 부작용 측면과 직각이라고 생각합니다.


요약

이 질문에 많은 관심을 기울 였으므로 주요 요점 / 답변을 요약하겠습니다. 먼저, "왜"가 "무슨 원인 ", "무슨 이유 ", "무엇을 목적으로 " 와 밀접한 관련이 있지만 미묘하게 다른 의미를 가질 수 있다는 점에 대해 약간의 왜곡이 있습니다 . 나는 그들이 왜 "왜"라는 의미로 대답을 그룹화 할 것입니다.

어떤 원인으로

주요 대답은 여기에서 유래 폴 드레이퍼 와, 마틴 J는 광범위한 답변으로 유사하지만 기여. 폴 드레이퍼의 대답은

동작이 무엇인지 정의되어 있지 않기 때문에 정의되지 않은 동작입니다.

그 대답은 C ++ 표준이 말하는 것을 설명 할 때 전반적으로 매우 좋습니다. 또한 같은 UB 일부 관련 사건을 해결 f(++i, ++i);하고 f(i=1, i=-1);. 첫 번째 관련 사례에서 첫 번째 인수가 i+1두 번째 i+2인지 아니면 두 번째 또는 그 반대인지 명확하지 않습니다 . 두 번째로, i함수 호출 후 1 또는 -1이어야하는지 명확하지 않습니다 . 이 두 경우 모두 다음 규칙에 속하기 때문에 UB입니다.

스칼라 객체의 부작용이 동일한 스칼라 객체의 다른 부작용에 비해 순서가 맞지 않으면 동작이 정의되지 않습니다.

따라서 f(i=-1, i=-1)프로그래머의 의도가 (IMHO) 명백하고 모호하지 않더라도 동일한 규칙에 속하기 때문에 UB이기도합니다.

폴 드레이퍼는 또한 그의 결론에서

동작을 정의했을 수 있습니까? 예. 정의 되었습니까? 아니.

"무슨 이유 / 목적이 f(i=-1, i=-1)정의되지 않은 행동 으로 남게 되었습니까?"

어떤 이유로 / 목적으로

C ++ 표준에는 약간의 감독 (심지어 부주의 할 수 있음)이 있지만, 많은 누락이 적절하고 합리적인 목적으로 사용됩니다. 목적이 종종 "컴파일러 작성기의 작업을 더 쉽게"만들거나 "더 빠른 코드"라는 것을 알고 있지만 , UB로 떠날만한 충분한 이유가 있는지에 대해 주로 관심이있었습니다 f(i=-1, i=-1) .

harmicsupercat 은 UB 의 이유 를 제공하는 주요 답변을 제공합니다 . Harmic은 표면적으로 원자 할당 작업을 여러 기계 명령어로 분리 할 수있는 최적화 컴파일러가 최적의 속도를 위해 해당 명령어를 추가로 인터리브 할 수 있다고 지적합니다. 이것은 매우 놀라운 결과로 이어질 수 i있습니다. 그의 시나리오에서 -2로 끝납니다! 따라서 고조파 는 연산에 순서가 지정되지 않은 경우 변수에 동일한 값 을 두 번 이상 할당하면 어떻게 악영향을 미칠 수 있는지 보여줍니다 .

supercat은 f(i=-1, i=-1)해야 할 일 을하려고하는 함정에 대한 관련 설명을 제공합니다 . 그는 일부 아키텍처에서는 동일한 메모리 주소에 대한 여러 동시 쓰기에 대한 엄격한 제한이 있다고 지적합니다. 우리가보다 사소한 것을 다루는 경우 컴파일러는 이것을 잡기가 어려울 수 있습니다 f(i=-1, i=-1).

davidf 는 또한 하모닉 과 매우 유사한 인터리빙 지침의 예를 제공합니다.

각 고조파, supercat 및 davidf의 예는 다소 고안되었지만, 함께 모아서 f(i=-1, i=-1)정의되지 않은 행동을해야하는 확실한 이유를 제공합니다 .

Paul Draper의 답변이 "원인"부분을 더 잘 다루었음에도 불구하고, 왜 모든 의미를 다루는 데 최선을 다했기 때문에 harmic의 답변을 받아 들였습니다.

다른 답변들

JohnB는 (일반 스칼라 대신) 오버로드 된 할당 연산자를 고려하면 문제가 발생할 수 있다고 지적합니다.


1
스칼라 객체는 스칼라 유형의 객체입니다. 3.9 / 9 참조 : "산술 유형 (3.9.1), 열거 유형, 포인터 유형, 멤버 유형에 대한 포인터 (3.9.2) std::nullptr_t및 이러한 유형의 cv 규정 버전 (3.9.3)을 통칭하여 스칼라 유형 이라고 합니다 . "
Rob Kennedy

1
어쩌면 페이지에 오류가있을 수 있으며, 실제로 같은 의미가 f(i-1, i = -1)있습니다.
Mr Lister

이 질문을 살펴보십시오 : stackoverflow.com/a/4177063/71074
Robert S. Barnes

@RobKennedy 감사합니다. "산술 유형"에 부울이 포함됩니까?
Nicu Stiurca

1
SchighSchagh 업데이트가 답변 섹션에 있어야합니다.
Grijesh Chauhan 2019

답변:


343

연산은 순서가 지정되지 않았으므로 할당을 수행하는 명령을 인터리브 할 수 없다는 것은 말할 것도 없습니다. CPU 아키텍처에 따라 그렇게하는 것이 가장 좋습니다. 참조 된 페이지는 다음을 나타냅니다.

A가 B보다 먼저 시퀀싱되지 않고 B가 A보다 먼저 시퀀싱되지 않으면 두 가지 가능성이 있습니다.

  • A와 B의 평가는 순서가 없습니다 : 그것들은 어떤 순서로든 수행 될 수 있으며 겹칠 수 있습니다 (단일 실행 스레드 내에서 컴파일러는 A와 B를 포함하는 CPU 명령어를 인터리브 할 수 있습니다)

  • A와 B의 평가는 불확실한 순서로 수행된다 : 어떤 순서로도 수행 될 수 있지만 겹치지 않을 수있다 : A는 B보다 먼저 완료되거나 B는 A보다 먼저 완료된다 평가됩니다.

수행되는 작업이 값 -1을 메모리 위치에 저장하는 것으로 가정하면 그 자체로는 문제를 일으키는 것처럼 보이지 않습니다. 그러나 컴파일러는 동일한 효과를 갖는 별도의 명령 세트로 컴파일러를 최적화 할 수 없지만 동일한 메모리 위치에서 다른 작업과 작업이 인터리브되면 실패 할 수 있습니다.

예를 들어, -1 in 값을로드하는 것과 비교하여 메모리를 0으로 설정 한 다음 감소시키는 것이 더 효율적이라고 가정하십시오.

f(i=-1, i=-1)

될 수 있습니다 :

clear i
clear i
decr i
decr i

이제 저는 -2입니다.

아마도 가짜 예일 수 있지만 가능합니다.


59
시퀀싱 규칙을 준수하면서 표현식이 실제로 예기치 않은 작업을 수행하는 방법에 대한 아주 좋은 예입니다. 예, 약간의 노력이 있었지만 처음에 묻고있는 코드도 깨졌습니다. :)
Nicu Stiurca

10
할당이 원자 연산으로 수행 되더라도 두 할당이 동시에 수행되어 메모리 액세스 충돌이 발생하여 오류가 발생하는 수퍼 스칼라 아키텍처를 생각할 수 있습니다. 이 언어는 컴파일러 작성자가 대상 시스템의 장점을 최대한 자유롭게 사용할 수 있도록 설계되었습니다.
ach

11
두 매개 변수에서 동일한 변수에 동일한 값을 할당해도 예기치 않은 결과가 발생할 수있는 예를 정말 좋아합니다. 두 할당이 순서가 지정되지 않았기 때문에 예상치 못한 결과가 발생합니다.
Martin J.

1
컴파일 된 코드가 항상 기대하는 것은 아니라는 점에서 + 1e + 6 (ok, +1)입니다. 옵티마이
Corey

3
Arm 프로세서에서로드 32 비트는 최대 4 개의 명령을 수행 할 수 있습니다. 최대 load 8bit immediate and shift4 번 수행됩니다. 일반적으로 컴파일러는이를 피하기 위해 테이블에서 숫자를 가져 오기 위해 간접 주소 지정을 수행합니다. (-1은 1 개의 명령으로 수행 할 수 있지만 다른 예를 선택할 수 있습니다).
ctrl-alt-delor 12

208

먼저, "스칼라 객체"는 같은 유형을 의미 int, float또는 포인터 (참조 ++ C에서 스칼라 객체 무엇입니까? ).


둘째, 더 분명해 보일 수 있습니다.

f(++i, ++i);

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

f(i = -1, i = -1);

덜 명확합니다.

약간 다른 예 :

int i;
f(i = 1, i = -1);
std::cout << i << "\n";

어떤 과제가 "마지막" i = 1또는 i = -1? 표준에 정의되어 있지 않습니다. 실제로는 그 방법 i이 될 수 있습니다 5(이 사건이 어떻게 일어날 수 있는지에 대한 완전히 그럴듯한 설명은 고조파의 대답을 참조하십시오). 또는 프로그램이 segfault 일 수 있습니다. 또는 하드 드라이브를 다시 포맷하십시오.

그러나 지금 당신은 묻습니다. "나의 예는 어떻습니까? -1두 과제에 같은 값 ( )을 사용했습니다 . 그 점에 대해 분명하지 않은 것은 무엇입니까?"

C ++ 표준위원회가 설명한 방식을 제외하고는 정확합니다.

스칼라 객체의 부작용이 동일한 스칼라 객체의 다른 부작용에 비해 순서가 맞지 않으면 동작이 정의되지 않습니다.

그들은 당신의 특별한 경우에 특별한 예외를 만들 있었지만 그렇지 않았습니다. (그리고 왜해야합니까? 어쩌면 어떤 용도로 사용됩니까?) i그래도 여전히 그렇습니다 5. 또는 하드 드라이브가 비어있을 수 있습니다. 따라서 귀하의 질문에 대한 답변은 다음과 같습니다.

동작이 무엇인지 정의되어 있지 않기 때문에 정의되지 않은 동작입니다.

(많은 프로그래머들이 "정의되지 않은"은 "무작위"또는 "예측할 수없는"을 의미한다고 생각하기 때문에 강조 할 가치가있다. 표준에 의해 정의되지 않은 것을 의미한다. 행동은 100 % 일관되고 여전히 정의되지 않을 수있다.)

동작을 정의했을 수 있습니까? 예. 정의 되었습니까? 따라서 "정의되지 않음"입니다.

말했다 즉, 컴파일러는 하드 드라이브를 포맷 것은 아닙니다 ... 그것은 것을 의미한다 "정의되지 않은" 그것은 여전히 표준을 준수하는 컴파일러 될 것이다. 실제로 g ++, Clang 및 MSVC가 모두 예상대로 작동합니다. 그들은 단지 "하지 않았다".


다른 질문은 C ++ 표준위원회왜이 부작용을 시퀀싱하지 않기로 선택했을까요? . 이 답변에는위원회의 역사와 의견이 포함됩니다. 또는 C ++ 에서이 부작용을 시퀀싱하지 않는 것이 좋은 점은 무엇입니까? 표준위원회의 실제 추론인지 여부에 상관없이 모든 정당성을 허용합니다. 여기 또는 programmers.stackexchange.com에서 이러한 질문을 할 수 있습니다.


9
@ hvd, 그렇습니다. 실제로 -Wsequence-pointg ++ 을 사용 하면 경고합니다.
Paul Draper

47
"g ++, Clang 및 MSVC가 모두 예상 한대로 작동 할 것입니다"라고 현대 컴파일러는 신뢰하지 않습니다. 그들은 악하다. 예를 들어, 이들은 정의되지 않은 동작임을 인식하고이 코드에 도달 할 수 없다고 가정합니다. 그들이 오늘 그렇게하지 않으면 내일 그렇게 할 수도 있습니다. 모든 UB는 시한 폭탄입니다.
코드 InChaos

8
@BlacklightShining "당신의 대답은 좋지 않기 때문에 나쁘다"는 매우 유용한 피드백이 아닌가?
Vincent van der Weele

13
@BobJarvis 컴파일은 정의되지 않은 동작에 직면하여 원격으로 올바른 코드를 생성 할 의무가 전혀 없다. TIt은이 코드가 호출 된 적이 없다고 가정하여 전체를 nop로 대체 할 수도 있습니다 (컴파일러는 실제로 UB에 대해 이러한 가정을합니다). 그러므로 나는 그러한 버그 리포트에 대한 올바른 반응은 "닫히고 의도 한대로 작동"할 수 있다고 말한다.
그리즐리

7
@SchighSchagh 때때로 용어의 표현 (표면에만 팽팽한 답으로 보이는)이 사람들에게 필요한 것입니다. 기술 사양에 익숙하지 않은 대부분의 사람들 은 대부분의 경우와 거리가 멀다는 undefined behavior것을 의미 something random will happen합니다.
이즈 카타

27

두 값이 동일하기 때문에 규칙에서 예외를 만들지 않는 실질적인 이유 :

// config.h
#define VALUEA  1

// defaults.h
#define VALUEB  1

// prog.cpp
f(i = VALUEA, i = VALUEB);

이것이 허용 된 경우를 고려하십시오.

몇 달 후, 변화해야 할 필요성이 생겼습니다.

 #define VALUEB 2

겉보기에 무해하지 않습니까? 그러나 갑자기 prog.cpp는 더 이상 컴파일되지 않습니다. 그러나 컴파일은 리터럴의 가치에 의존해서는 안된다고 생각합니다.

결론 : 규칙의 예외는 상수의 값 (유형이 아닌)에 따라 성공적으로 컴파일되므로 예외는 없습니다.

편집하다

@HeartWare는A DIV B 일부 언어에서는 B0 일 때 양식의 상수 표현식이 허용되지 않으며 컴파일이 실패 한다고 지적했습니다 . 따라서 상수를 변경하면 다른 곳에서 컴파일 오류가 발생할 수 있습니다. IMHO는 불행합니다. 그러나 그러한 것들을 피할 수없는 것으로 제한하는 것이 좋습니다.


물론이 예제 에서는 정수 리터럴을 사용합니다. 당신 f(i = VALUEA, i = VALUEB);은 분명히 정의되지 않은 행동의 가능성이 있습니다. 식별자 뒤에있는 값을 실제로 코딩하지 않기를 바랍니다.
Wolf

3
@Wold 그러나 컴파일러는 전 처리기 매크로를 보지 못합니다. 그리고 그렇지 않은 경우에도 소스 코드가 int 상수를 1에서 2로 변경할 때까지 소스 코드가 컴파일되는 프로그래밍 언어의 예를 찾기가 어렵습니다. 왜 그 코드가 같은 값으로도 깨졌습니다.
Ingo

예, 컴파일에는 매크로가 표시되지 않습니다. 그러나 이것이 질문입니까?
Wolf

1
귀하의 답변에 요점이 없습니다. 고조파의 답변 과 OP의 의견을 읽으 십시오.
Wolf

1
할 수 SomeProcedure(A, B, B DIV (2-A))있습니다. 어쨌든, 언어가 CONST가 컴파일 타임에 완전히 평가되어야한다고 말하면 물론 내 주장은 그 경우에 유효하지 않습니다. 컴파일 타임과 런타임의 구분이 어떻게 든 흐려지기 때문입니다. 우리가 글을 쓰면 알 수 CONST C = X(2-A); FUNCTION X:INTEGER(CONST Y:INTEGER) = B/Y; 있을까요? 아니면 기능이 허용되지 않습니까?
Ingo

12

혼란은 상수 값을 지역 변수에 저장하는 것이 C가 실행되도록 설계된 모든 아키텍처에서 하나의 원자 명령이 아니라는 것입니다. 이 경우 코드가 실행되는 프로세서는 컴파일러보다 중요합니다. 예를 들어, 각 명령어가 완전한 32 비트 상수를 전달할 수없는 ARM에서 int를 변수에 저장하려면 하나 이상의 명령어가 필요합니다. 한 번에 8 비트 만 저장할 수 있고 32 비트 레지스터에서 작동해야하는이 의사 코드의 예는 int32입니다.

reg = 0xFF; // first instruction
reg |= 0xFF00; // second
reg |= 0xFF0000; // third
reg |= 0xFF000000; // fourth
i = reg; // last

컴파일러가 최적화를 원한다면 동일한 시퀀스를 두 번 인터리브 할 수 있으며 i에 어떤 값이 쓰여질 지 모른다는 것을 상상할 수 있습니다. 그가 똑똑하지 않다고 가정 해 봅시다.

reg = 0xFF;
reg |= 0xFF00;
reg |= 0xFF0000;
reg = 0xFF;
reg |= 0xFF000000;
i = reg; // writes 0xFF0000FF == -16776961
reg |= 0xFF00;
reg |= 0xFF0000;
reg |= 0xFF000000;
i = reg; // writes 0xFFFFFFFF == -1

그러나 내 테스트에서 gcc는 동일한 값이 두 번 사용되어 한 번 생성되고 이상한 일을하지 않는다는 것을 인식하기에 친절합니다. 나는 -1, -1을 얻지 만 상수도조차도 분명하지 않은 것으로 간주하는 것이 중요하므로 내 예제는 여전히 유효합니다.


ARM에서는 컴파일러가 테이블에서 상수를로드한다고 가정합니다. 당신이 묘사하는 것은 MIPS와 비슷합니다.
ach

1
@AndreyChernyakhovskiy Yep,하지만 단순히 -1(컴파일러가 어딘가에 저장 한) 것이 아니라 오히려 3^81 mod 2^32그렇지만 일정하지 않은 경우 컴파일러는 여기에서 수행 된 작업을 정확하게 수행 할 수 있으며 최적화의 일부 수단으로 호출 시퀀스를 인터리브합니다 기다리는 것을 피하기 위해.
yo '

@tohecz, 예, 이미 확인했습니다. 실제로 컴파일러는 테이블에서 모든 상수를로드하기에는 너무 똑똑합니다. 어쨌든 동일한 레지스터를 사용하여 두 상수를 계산하지는 않습니다. 이것은 분명히 정의 된 행동을 '정의 해제'할 것입니다.
ach

@AndreyChernyakhovskiy 그러나 당신은 아마 "세계의 모든 C ++ 컴파일러 프로그래머"는 아닐 것이다. 계산에만 사용할 수있는 3 개의 짧은 레지스터가있는 머신이 있습니다.
yo '

@tohecz, f(i = A, j = B)where ijare two objects 인 예제를 고려하십시오 . 이 예에는 UB가 없습니다. 3 개 짧은 레지스터를 가진 기계 컴파일러의 두 값 믹스에 대한 변명입니다 AB이 프로그램의 의미를 깰 것이기 때문에, (같은 @ davidf의 대답에 표시) 같은 레지스터를.
ach

11

"유용한"컴파일러가 완전히 예상치 못한 동작을 유발할 수있는 무언가를 수행 할 수있는 몇 가지 이유가있을 경우 동작은 일반적으로 정의되지 않은 것으로 지정됩니다.

변수가 여러 번 기록되어 별개의 시간에 기록이 이루어 지도록 보장하지 않는 경우, 일부 종류의 하드웨어는 이중 포트 메모리를 사용하여 여러 "저장"작업을 다른 주소로 동시에 수행 할 수 있습니다. 그러나 일부 듀얼 포트 메모리는 기록 된 값이 일치하는지 여부에 관계없이 두 저장소가 동일한 주소를 동시에 누르는 시나리오를 명시 적으로 금지합니다.. 그러한 머신의 컴파일러가 동일한 변수를 작성하는 순서없는 두 번의 시도를 발견하면, 컴파일을 거부하거나 두 쓰기가 동시에 스케줄 될 수 없도록 할 수 있습니다. 그러나 액세스 중 하나 또는 둘 다가 포인터 또는 참조를 통해 수행되는 경우 컴파일러가 두 쓰기가 동일한 저장 위치에 도달 할 수 있는지 여부를 항상 알 수는 없습니다. 이 경우 쓰기가 동시에 예약되어 액세스 시도에 하드웨어 트랩이 발생할 수 있습니다.

물론 누군가가 그러한 플랫폼에서 C 컴파일러를 구현할 수 있다는 사실은 원자 적으로 처리하기에 충분히 작은 유형의 저장소를 사용할 때 이러한 동작이 하드웨어 플랫폼에서 정의되어서는 안된다고 제안하지 않습니다. 컴파일러가 알지 못하는 경우 서로 다른 두 가지 값을 순서대로 저장하지 않으면 이상이 발생할 수 있습니다. 예를 들면 다음과 같습니다.

uint8_t v;  // Global

void hey(uint8_t *p)
{
  moo(v=5, (*p)=6);
  zoo(v);
  zoo(v);
}

컴파일러가 "moo"에 대한 호출을 인라인하고 "v"를 수정하지 않는다고 말할 수 있으면 5 대 v를 저장 한 다음 6 대 * p를 저장 한 다음 5를 "zoo"로 전달한 다음 v의 내용을 "zoo"로 전달하십시오. "zoo"가 "v"를 수정하지 않으면 두 호출에 다른 값을 전달할 방법이 없어야하지만 어쨌든 쉽게 발생할 수 있습니다. 반면에 두 상점 모두 같은 값을 쓸 경우 이러한 기이함이 발생할 수 없으며 대부분의 플랫폼에서 구현이 기묘한 일을하는 합리적인 이유가 없을 것입니다. 불행히도, 일부 컴파일러 작성자는 "표준이 허용하기 때문에"이외의 어리석은 행동에 대한 변명이 필요하지 않으므로 이러한 경우조차 안전하지 않습니다.


9

경우 대부분의 구현에서 결과가 동일하다는 사실 은 부수적입니다. 평가 순서는 아직 정의되지 않았습니다. 고려 f(i = -1, i = -2): 여기, 순서가 중요합니다. 귀하의 예에서 중요하지 않은 유일한 이유는 두 값이 모두 사고가 발생한 것입니다 -1.

식이 정의되지 않은 동작이있는 식으로 지정된 경우, 악의적으로 호환되는 컴파일러 f(i = -1, i = -1)는 실행 을 평가 하고 중단 할 때 부적절한 이미지를 표시 할 수 있지만 여전히 완전히 올바른 것으로 간주됩니다. 운 좋게도 내가 아는 컴파일러는 없습니다.


8

함수 인수 표현식의 시퀀싱과 관련된 유일한 규칙은 다음과 같습니다.

3) 함수를 호출 할 때 (함수가 인라인인지 여부와 명시적인 함수 호출 구문 사용 여부) 인수 표현식 또는 호출 된 함수를 지정하는 접미사 표현식과 관련된 모든 값 계산 및 부작용은 다음과 같습니다. 호출 된 함수의 본문에서 모든 표현식 또는 명령문을 실행하기 전에 순서화됩니다.

이것은 인수 표현식 사이의 시퀀싱을 정의하지 않으므로이 경우에 끝납니다.

1) 스칼라 객체의 부작용이 동일한 스칼라 객체의 다른 부작용에 비해 순서가 맞지 않으면 동작이 정의되지 않습니다.

실제로 대부분의 컴파일러에서 인용 한 예제는 "하드 디스크 지우기"및 기타 이론적으로 정의되지 않은 동작 결과와는 달리 제대로 실행됩니다.
그러나 할당 된 두 값이 동일하더라도 특정 컴파일러 동작에 따라 달라 지므로 책임이 있습니다. 또한 다른 값을 할당하려고하면 결과가 "정확하게"정의되지 않은 것입니다.

void f(int l, int r) {
    return l < -1;
}
auto b = f(i = -1, i = -2);
if (b) {
    formatDisk();
}

8

C ++ 17 은보다 엄격한 평가 규칙을 정의합니다. 특히, 함수 인수를 지정합니다 (특정 순서는 아님).

N5659 §4.6:15
평가 ABAB 보다 먼저 시퀀싱 될 때 또는 BA 보다 먼저 시퀀싱 될 때 불확실하게 시퀀싱 되지만, 어느 것이 미지정인지는 확실하지 않습니다. [ 참고 : 불확실한 순서의 평가는 겹칠 수 없지만 먼저 실행할 수 있습니다. — 끝 참고 ]

N5659 § 8.2.2:5
모든 관련 값 계산 및 부작용을 포함하여 매개 변수의 초기화는 다른 매개 변수의 초기화와 관련하여 결정되지 않습니다.

그것은 전에 UB 일 수있는 경우를 허용합니다 :

f(i = -1, i = -1); // value of i is -1
f(i = -1, i = -2); // value of i is either -1 or -2, but not specified which one

2
c ++ 17 이 업데이트를 추가해 주셔서 감사합니다 . ;)
Yakk-Adam Nevraumont

이 답변에 감사드립니다. 약간의 후속 조치 : f서명이 f(int a, int b)인 경우 C ++ 17은 그것을 보장 하고 두 번째 경우 a == -1b == -2같이 호출됩니까?
Nicu Stiurca

예. 우리는 매개 변수가있는 경우 ab, 다음 중 하나를 i-then-는 a이후, -1로 초기화 i-then-가 b-2로 초기화, 또는 주변의 방법입니다. 두 경우 모두, 우리와 끝까지 a == -1하고 b == -2. 적어도 이것은 " 모든 관련 값 계산 및 부작용을 포함하여 매개 변수의 초기화가 다른 매개 변수의 초기화와 관련하여 결정되지 않은 순서로 "읽는 방식 입니다.
AlexD

나는 그것이 C 이후로 영원히 같았다고 생각합니다.
fuz

5

할당 연산자가 오버로드 될 수 있으며이 경우 순서가 중요 할 수 있습니다.

struct A {
    bool first;
    A () : first (false) {
    }
    const A & operator = (int i) {
        first = !first;
        return * this;
    }
};

void f (A a1, A a2) {
    // ...
}


// ...
A i;
f (i = -1, i = -1);   // the argument evaluated first has ax.first == true

1
충분히 사실이지만, 다른 사람들이 지적한 스칼라 유형 에 대한 질문은 본질적으로 int family, float family 및 pointers를 의미합니다.
Nicu Stiurca

이 경우 실제 문제는 대입 연산자가 상태 저장이므로 정기적으로 변수를 조작하더라도 이와 같은 문제가 발생하기 쉽다는 것입니다.
AJMansfield

2

이것은 "int 나 float 같은 것 외에"스칼라 객체 "가 무엇을 의미하는지 잘 모르겠습니다"라고 대답합니다.

"스칼라 객체"는 "스칼라 타입 객체"또는 "스칼라 타입 변수"의 약어로 해석합니다. 그런 다음 pointer, enum(상수) 스칼라 타입이다.

이 문서는 Scalar Types 의 MSDN 기사입니다 .


이것은 "링크 전용 답변"과 비슷합니다. 해당 링크의 해당 비트를이 답변 (블록 인용)으로 복사 할 수 있습니까?
Cole Johnson

1
@ColeJohnson 이것은 링크 전용 답변이 아닙니다. 링크는 추가 설명을위한 것입니다. 내 대답은 "포인터", "열거"입니다.
Peng Zhang

귀하의 답변 링크 전용 답변 이라고 말하지 않았습니다 . 나는 "[one]처럼 읽었다" 고 말했다 . 도움말 섹션에서 링크 전용 답변을 원하지 않는 이유를 읽어보십시오. 그 이유는 Microsoft가 사이트에서 URL을 업데이트하면 해당 링크가 끊어지기 때문입니다.
Cole Johnson

1

실제로 컴파일러가 i동일한 값을 두 번 할당 한 것을 컴파일러가 확인한다는 사실에 의존하지 않는 이유 가 있기 때문에 단일 할당으로 대체 할 수 있습니다. 표현이 있으면 어떻게 되나요?

void g(int a, int b, int c, int n) {
    int i;
    // hey, compiler has to prove Fermat's theorem now!
    f(i = 1, i = (ipow(a, n) + ipow(b, n) == ipow(c, n)));
}

1
Fermat의 정리를 증명할 필요는 없습니다 :에 할당 1하십시오 i. 두 인수가 모두 지정 1하고 이것이 "올바른"일을하거나 인수가 다른 값을 지정하며 정의되지 않은 동작이므로 선택이 여전히 허용됩니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.