i = i ++ + 1; C ++ 17에서 합법적입니까?


186

정의되지 않은 동작을 시작하기 전에 N4659 (C ++ 17)에 명시 적으로 나열되어 있습니다.

  i = i++ + 1;        // the value of i is incremented

그러나 N3337 (C ++ 11)

  i = i++ + 1;        // the behavior is undefined

무엇이 바뀌 었습니까?

내가 수집 할 수있는 것, [N4659 basic.exec]

명시된 경우를 제외하고, 개별 연산자의 피연산자 및 개별 표현식의 하위 표현식 평가는 순서가 없습니다. [...] 연산자의 피연산자의 값 계산은 연산자 결과의 값 계산 전에 순서화됩니다. 메모리 위치에 대한 부작용이 동일한 메모리 위치에있는 다른 부작용이나 동일한 메모리 위치에있는 객체의 값을 사용한 값 계산과 관련하여 순서가없고 잠재적으로 동시 적이 지 않은 경우에는 동작이 정의되지 않습니다.

여기서 [N4659 basic.type]에 정의되어 있습니다 .

사소 복사 가능한 유형의 값의 표현은 결정 객체 표현의 비트 세트 인 값을 값의 구현 된 집합의 하나 개의 개별 요소,

가입일 [basic.exec N3337]

명시된 경우를 제외하고, 개별 연산자의 피연산자 및 개별 표현식의 하위 표현식 평가는 순서가 없습니다. [...] 연산자의 피연산자의 값 계산은 연산자 결과의 값 계산 전에 순서화됩니다. 스칼라 오브젝트의 부작용이 동일한 스칼라 오브젝트의 다른 부작용이나 동일한 스칼라 오브젝트의 값을 사용한 값 계산과 관련하여 순서가 지정되지 않은 경우, 동작은 정의되지 않습니다.

마찬가지로, 값은 [N3337 basic.type]에 정의되어 있습니다 .

사소하게 복사 가능한 유형의 경우, 값 표현은 구현을 정의한 값 세트의 개별 요소 인 value 를 결정하는 오브젝트 표현의 비트 세트입니다.

중요하지 않은 동시성을 언급하고 스칼라 객체 대신 메모리 위치 를 사용한다는 점을 제외하고는 동일 합니다 .

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

예제에 영향을 미치지 않습니다.

가입일 [expr.ass N4659]

대입 연산자 (=)와 복합 대입 연산자는 모두 오른쪽에서 왼쪽으로 그룹화됩니다. 모두 왼쪽 피연산자로 수정 가능한 lvalue가 필요하고 왼쪽 피연산자를 참조하는 lvalue를 반환합니다. 왼쪽 피연산자가 비트 필드 인 경우 모든 결과는 비트 필드입니다. 모든 경우에, 할당은 오른쪽과 왼쪽 피연산자의 값 계산 후와 할당 식의 값 계산 전에 순서화됩니다. 오른쪽 피연산자는 왼쪽 피연산자보다 먼저 시퀀싱됩니다.

가입일 [expr.ass N3337]

대입 연산자 (=)와 복합 대입 연산자는 모두 오른쪽에서 왼쪽으로 그룹화됩니다. 모두 왼쪽 피연산자로 수정 가능한 lvalue가 필요하고 왼쪽 피연산자를 참조하는 lvalue를 반환합니다. 왼쪽 피연산자가 비트 필드 인 경우 모든 결과는 비트 필드입니다. 모든 경우에, 할당은 오른쪽과 왼쪽 피연산자의 값 계산 후와 할당 식의 값 계산 전에 순서화됩니다.

유일한 차이점은 N3337에 마지막 문장이 없다는 것입니다.

그러나 왼쪽 피연산자 iid-expression 이 lvalue 인 것처럼 "다른 부작용" 이나 "같은 스칼라 객체의 값 사용 " 이 아니기 때문에 마지막 문장은 중요하지 않습니다 .


23
이유는 다음과 같습니다. C ++ 17에서는 오른쪽 피연산자가 왼쪽 피연산자보다 먼저 시퀀싱됩니다. C ++ 11에는 그러한 시퀀싱이 없었습니다. 정확히 당신의 질문은 무엇입니까?
Robᵩ

4
@ Robᵩ 마지막 문장을보십시오.
통행인

7
누구든지이 변화에 대한 동기와 관련이 있습니까? 정적 분석기가와 같은 코드에 직면했을 때 "당신이하고 싶지 않다"고 말할 수 있기를 바랍니다 i = i++ + 1;.

7
@NeilButterworth는 p0145r3.pdf : "관용적 C ++에 대한 정제 표현 평가 순서 정립"에서 발췌했습니다.
xaizek

9
@NeilButterworth, 섹션 번호 2는 이것이 직관적이지 않으며 전문가조차도 모든 경우에 옳은 일을하지 못한다고 말합니다. 그것은 거의 모든 동기입니다.
xaizek

답변:


144

C ++ 11에서 "할당"행위, 즉 LHS 수정의 부작용 은 오른쪽 피연산자 의 값 계산 후에 시퀀싱 됩니다. 이는 상대적으로 "약한"보증 입니다. RHS의 값 계산 과 관련하여 만 시퀀싱을 생성합니다 . 부작용의 발생은 가치 계산의 일부가 아니기 때문에 RHS에 존재할 수있는 부작용 에 대해서는 아무 것도 언급 하지 않습니다 . C ++ 11의 요구 사항은 할당 행위와 RHS의 부작용 사이에 상대적인 시퀀싱을 확립하지 않는다. 이것이 UB의 잠재력을 창출하는 것입니다.

이 경우 유일한 희망은 RHS에 사용되는 특정 운영자가 수행 한 추가 보증입니다. RHS가 prefix를 사용한 경우 ++접두사 형식의 고유 한 시퀀싱 속성 ++이이 예에서 날짜를 저장했을 것입니다. 그러나 postfix ++는 다른 이야기입니다 : 그것은 그런 보장을하지 않습니다. C ++ 11 에서이 예제에서 부작용 =과 postfix의 부작용은 ++서로 관련이없는 순서로 끝납니다. 그리고 그것은 UB입니다.

C ++ 17에서는 할당 연산자의 사양에 추가 문장이 추가되었습니다.

오른쪽 피연산자는 왼쪽 피연산자보다 먼저 시퀀싱됩니다.

위와 함께 사용하면 매우 강력합니다. LHS에서 발생하는 모든 것 보다 RHS에서 발생하는 모든 것 (모든 부작용 포함)을 순서대로 나열 합니다. 실제 할당은 LHS (및 RHS) 후에 시퀀싱되므로, 추가 시퀀싱은 RHS에 존재하는 부작용으로부터 할당 행위를 완전히 분리시킵니다. 이 강력한 시퀀싱은 위의 UB를 제거하는 것입니다.

(@John Bollinger의 의견을 고려하여 업데이트되었습니다.)


3
해당 발췌 부분의 "왼손 피연산자"에 포함 된 효과에 "실제 할당 행위"를 포함시키는 것이 실제로 맞습니까? 표준은 실제 과제의 순서에 대해 별도의 언어를 가지고 있습니다. 나는 당신이 제시 한 발췌 부분을 왼쪽과 오른쪽 하위 표현의 시퀀싱으로 제한합니다.이 부분의 나머지 부분과 함께 충분하지 않은 것으로 보입니다. OP의 진술의 정의.
존 볼린저

11
수정 : 실제 할당은 여전히 ​​왼쪽 피연산자의 값 계산 후에 시퀀싱되고 왼쪽 피연산자의 평가는 오른쪽 피연산자의 (완전한) 평가 후에 시퀀싱되므로 변경 사항이 OP를 명확하게 정의하기에 충분합니다. 물었다. 세부 사항을 혼란스럽게하지만 코드마다 다른 의미를 가질 수 있으므로 중요합니다.
존 볼린저

3
@ JohnBollinger : 표준 작성자가 심지어 코드 생성의 효율성을 손상시키고 역사적으로 필요하지 않은 변경을 할 것이 궁금하지만 부재가 훨씬 큰 문제가있는 다른 행동을 정의하는 데 관심이 있으며 효율성에 의미있는 장애는 거의 없습니다.
supercat

1
@Kaz : 복합 할당의 경우 오른쪽 다음에 왼쪽 값 평가를 수행하면와 x -= y;같은 mov eax,[y] / sub [x],eax것이 아닌 처리 될 수 있습니다 mov eax,[x] / neg eax / add eax,[y] / mov [x],eax. 나는 그것에 대해 특이한 것을 보지 못했습니다. 순서를 지정 해야하는 경우 가장 효율적인 순서는 왼쪽 개체를 먼저 식별 한 다음 오른쪽 피연산자를 평가 한 다음 왼쪽 개체 의 을 평가하는 데 필요한 모든 계산을 수행 하는 것이지만 용어가 필요합니다. 왼쪽 개체의 id'ty를 해결하는 행위.
supercat

1
@Kaz는 다음과 같은 경우에 xy있었다 volatile, 그 부작용이있을 것입니다. 또한, 같은 고려 사항에 적용 할 x += f();경우, f()수정 x.
supercat

33

당신은 새로운 문장을 식별

오른쪽 피연산자는 왼쪽 피연산자보다 먼저 시퀀싱됩니다.

왼쪽 피연산자의 lvalue 평가가 관련이 없음을 올바르게 식별했습니다. 그러나 시퀀싱 이전 은 전이 관계로 지정됩니다. 따라서 전체 오른쪽 피연산자 (사후 증가 포함) 할당 전에 순서화됩니다. C ++ 11에서는 할당 전에 올바른 피연산자 의 값 계산 만 시퀀싱되었습니다.


7

이전 C ++ 표준 및 C11에서 대입 연산자 텍스트의 정의는 다음 텍스트로 끝납니다.

피연산자의 평가는 순서가 없습니다.

피연산자의 부작용은 순서가 없으므로 동일한 변수를 사용하는 경우 분명히 정의되지 않은 동작입니다.

이 텍스트는 C ++ 11에서 간단하게 제거되어 다소 모호합니다. UB입니까, 그렇지 않습니까? 이것은 C ++ 17에서 다음과 같이 추가되었습니다.

오른쪽 피연산자는 왼쪽 피연산자보다 먼저 시퀀싱됩니다.


부수적으로, 오래된 표준에서도 C99의 예는 매우 명확했습니다.

피연산자의 평가 순서는 지정되어 있지 않습니다. 할당 연산자의 결과를 수정하거나 다음 시퀀스 포인트 이후에 액세스하려고하면 동작이 정의되지 않습니다.

기본적으로 C11 / C ++ 11에서는이 텍스트를 제거 할 때 엉망이되었습니다.


1

이것은 다른 답변에 대한 추가 정보이며 아래 코드가 자주 묻는 것처럼 게시하고 있습니다 .

다른 답변의 설명은 정확하며 이제 잘 정의되어 있으며 저장된 값을 변경하지 않는 다음 코드에도 적용됩니다 i.

i = i++;

이것은 + 1붉은 청어이며 표준에서 예제에서 왜 그것을 사용했는지는 확실하지 않지만 C ++ 11 이전의 메일 링리스트에서 논쟁하는 사람들을 기억 + 1합니다. 손 쪽. 확실히 그중 어느 것도 C ++ 17에 적용되지 않으며 (아마도 어떤 버전의 C ++에도 적용되지 않을 것입니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.