x = x ++가 정의되지 않은 이유는 무엇입니까?


19

x시퀀스 포인트 사이에서 두 번 수정되기 때문에 정의되지 않습니다 . 표준은 정의되지 않았으므로 정의되어 있지 않습니다.
내가 알고있는 것

그런데 왜?

내 이해는 이것을 금지하면 컴파일러가 더 잘 최적화 할 수 있다는 것입니다. 이것은 C가 발명되었을 때 의미가 있었지만 이제는 약한 주장처럼 보입니다.
만약 우리가 오늘 C를 재발 명한다면, 우리는 이런 식으로할까요, 아니면 더 잘할 수 있습니까?
또는 더 깊은 문제가있어 그러한 표현에 대해 일관된 규칙을 정의하기 어렵 기 때문에 금지하는 것이 가장 좋습니다.

오늘 C를 재창조했다고 가정 해 봅시다 과 같은 표현에 대한 간단한 규칙을 제안하고 싶습니다 x=x++. 기존 규칙보다 잘 작동하는 것 같습니다.
기존 규칙 또는 다른 제안과 비교하여 제안 된 규칙에 대한 귀하의 의견을 듣고 싶습니다.

제안 된 규칙 :

  1. 시퀀스 포인트 사이에서 평가 순서는 지정되지 않습니다.
  2. 부작용은 즉시 발생합니다.

정의되지 않은 동작이 없습니다. 식은이 값 또는 그 값으로 평가되지만 하드 디스크를 포맷하지는 않습니다 (이상하게도 x=x++하드 디스크를 포맷 하는 구현은 본 적이 없습니다 ).

표현식 예

  1. x=x++-잘 정의되어 있으며 변경되지 않습니다 x.
    먼저, x( x++평가 될 때 즉시) 증가한 다음 이전 값이에 저장됩니다 x.

  2. x++ + ++x- x두 번 증가 하고로 평가됩니다 2*x+2.
    어느 쪽이든 먼저 평가할 수 있지만 결과는 x + (x+2)(왼쪽부터) 또는 (x+1) + (x+1)(오른쪽부터)입니다.

  3. x = x + (x=3)-지정되지 않은 x경우 x+3또는로 설정하십시오 6.
    오른쪽이 먼저 평가되면 x+3입니다. x=3먼저 평가 되는 것도 가능합니다 3+3. 두 경우 모두 x=3할당 x=3이 평가 될 때 즉시 할당 이 수행 되므로 저장된 값은 다른 할당으로 덮어 씁니다.

  4. x+=(x=3)-잘 정의되어 있고, x6으로 설정 됩니다.
    위의 표현에 대한 축약 형이라고 주장 할 수 있습니다.
    그러나 나는 두 부분 (읽기 , 평가 , 추가 및 새로운 값 저장)이 아니라 +=이후 x=3에 실행되어야 한다고 말하고 싶습니다 .xx=3

장점은 무엇입니까?

몇몇 의견은이 점을 지적했다.
필자는 x=x++정상적인 코드에서 와 같은 표현식을 사용해야 한다고 생각하지 않습니다 .
사실, 난 훨씬 더 엄격한보다 해요 - 나는 유일한 좋은 사용을 생각 x++x++;혼자.

그러나 언어 규칙은 가능한 한 단순해야한다고 생각합니다. 그렇지 않으면 프로그래머는 이해하지 못합니다. 시퀀스 포인트 사이에서 변수를 두 번 변경하는 것을 금지하는 규칙은 대부분의 프로그래머가 이해하지 못하는 규칙입니다.

매우 기본적인 규칙은 다음과 같습니다.
A가 유효하고 B가 유효하고 올바른 방식으로 결합되면 결과가 유효합니다.
x유효한 L- 값이고 x++유효한 표현이며 =L- 값과 표현식을 결합하는 유효한 방법이므로 어떻게 x=x++합법적이지 않습니까?
C 표준은 여기서 예외를 만들고이 예외는 규칙을 복잡하게합니다. stackoverflow.com 을 검색 하여이 예외가 사람들을 혼동하는 정도를 확인할 수 있습니다 .
그래서 나는 말합니다-이 혼란을 제거하십시오.

=== 답변 요약 ===

  1. 왜 그럴까요?
    위의 섹션에서 설명하려고했습니다 .C 규칙을 간단하게 만들고 싶습니다.

  2. 최적화 가능성 :
    컴파일러에서 약간의 자유가 필요하지만 중요하다는 것을 확신시키는 것을 보지 못했습니다.
    대부분의 최적화는 여전히 수행 할 수 있습니다. 예를 들어, a=3;b=5;표준에서 순서를 지정하더라도 순서를 바꿀 수 있습니다. 과 같은 표현식 a=b[i++]은 여전히 ​​유사하게 최적화 될 수 있습니다.

  3. 기존 표준을 변경할 수 없습니다.
    인정할 수 없습니다. 나는 실제로 표준과 컴파일러를 바꿀 수 있다고 생각하지 않았습니다. 나는 일이 다르게 이루어질 수 있는지 생각하고 싶었습니다.


10
왜 중요한이 당신에게있다? 그것을 정의 해야 한다면 왜 그렇게 해야 하는가 ? x자신 에게 할당 할 점이별로 없으며 증분 x을 원한다면 x++;할당 할 필요가 없다고 말할 수 있습니다 . 나는 그것이 일어날 일을 기억하기가 어렵 기 때문에 그것을 정의 해서는 안된다고 말하고 싶습니다.
Caleb

4
제 생각에는 이것이 좋은 질문입니다 ( "어떤 사람들은 사물을있는 그대로보고 왜 그런지 묻습니다. 나는 결코 없었던 것을 꿈꾸며 왜 그런지 묻지 않습니다"). C 구문에 대한 질문이 아니라 C 구문을 예로 사용하여 언어 디자인에 대한 질문입니다. 개인적으로 우리가 x ++ + ++ x 또는 x = x ++와 같은 표현식에 대해 동작을 정의하지 않은 이유는 단순히 오해의 가능성이 있기 때문이라고 생각합니다.
Jamie Taylor

5
@ugoren : 왜 결과를 예측해야합니까? 이와 같은 코드를 작성하더라도 첫 번째 코드 검토에서 거부 될 수 있습니다. 따라서 동작을 정의하고 최적화 프로그램이이를 최적화 할 수있는 최상의 기회를 제공 할 필요가 없습니다. 모든 예제에서 코드베이스에 추가하면 누군가를 쏠 것이라고 제안합니다.
Martin York

3
더 흥미로운 질문은 왜 이것을 쓰는 것이 오류가 아닌지 알 수 있습니까? 분명히 컴파일러는 정의되지 않은 동작을 감지 할 수 있으므로 사용자가 실제로 원하는 것이 아닐 수 있으므로 오류가 아닌 이유는 무엇입니까? 나는 정의되지 않은 행동의 일부 사례는 감지하기 어렵다는 것을 이해하지만 이것은 아니다.
JohnB

3
" 시퀀스 포인트 사이에서 변수를 두 번 변경하는 규칙은 대부분의 프로그래머가 이해하지 못하는 규칙입니다. "-이에 대한 증거가 있습니까? 프로그래머가 규칙에 대해 알지 못했기 때문에 내가 본 대부분의 질문이 제기되었습니다 . 설명 된 후에도 대부분 이해 하지 못했다는 증거가 있습니까?
확보하십시오

답변:


24

어쩌면 먼저 정의해야하는 질문에 대답해야합니까? 추가적인 부작용이있는 표현식을 허용함으로써 프로그래밍 스타일, 가독성, 유지 보수성 또는 성능에 이점이 있습니까? 입니다

y = x++ + ++x;

보다 읽기 쉬운

y = 2*x + 2;
x += 2;

이러한 변경이 매우 기본적이고 기존 코드 기반을 위반한다는 점을 감안할 때.


1
질문에 "why"섹션을 추가했습니다. 필자는 이러한 표현을 사용하는 것을 권장하지는 않지만 표현의 의미를 알려주는 간단한 규칙에 관심이 있습니다.
ugoren

또한이 변경은 정의되지 않은 동작을 호출하지 않는 한 기존 코드를 손상시키지 않습니다. 틀린 점 있으면 지적 해주세요.
ugoren

3
더 철학적 인 대답은 현재 정의되어 있지 않습니다. 프로그래머가 사용하지 않으면 코드가 없어야하기 때문에 이러한 표현식을 이해할 필요가 없습니다. 그것들을 이해해야 할 필요가 있다면 분명히 정의되지 않은 행동에 의존하는 많은 코드가 있어야합니다. )
보안

1
정의에 따르면 행동을 정의하기 위해 기존 코드베이스를 위반하지 않습니다. 그들이 UB를 포함했다면, 그것들은 정의상 이미 고장났습니다.
DeadMG 2016 년

1
@ugoren : "왜"섹션이 여전히 실용적인 질문에 답하지 않습니다 : 코드에서이 이상한 표현을 원하십니까? 그것에 대한 설득력있는 대답을 얻을 수 없다면, 전체 토론은 무의미합니다.
Mike Baranczak

20

이 정의되지 않은 동작이 더 나은 최적화를 가능하게한다는 주장은 오늘날 약하지 않습니다 . 사실, 그것은 C가 새로운 것이었던 것 보다 오늘날 훨씬 더 강합니다 .

C가 새로 등장했을 때 더 나은 최적화를 위해이를 활용할 수있는 기계는 대부분 이론적 인 모델이었습니다. 사람들은 CPU가 다른 명령과 병렬로 실행될 수있는 명령에 대해 CPU에 지시하는 CPU를 구축 할 가능성에 대해 이야기했습니다. 그들은 이것이 정의되지 않은 동작을 갖도록 허용한다는 것은 그러한 CPU에서 실제로 존재한다면 명령의 "증가"부분이 나머지 명령 스트림과 동시에 실행되도록 예약 할 수 있음을 의미한다는 사실을 지적했다. 그들이 이론에 대해 옳았지만, 당시에는이 가능성을 실제로 활용할 수있는 하드웨어가 거의 없었습니다.

그것은 더 이상 이론적 인 것이 아닙니다. 이제 실제로이를 활용할 수있는 생산 및 광범위하게 사용되는 하드웨어 (예 : Itanium, VLIW DSP)가 있습니다 . 그들은 정말 컴파일러가 지정하는 지침의 X, Y 및 Z 모두 병렬로 실행될 수있는 명령 스트림을 생성 할 수 있습니다. 이것은 더 이상 이론적 인 모델이 아니며 실제 작업을 수행하는 실제 하드웨어입니다.

이 정의 된 동작을 만드는 IMO 는 문제 에 대한 최악의 "솔루션"에 가깝습니다 . 이와 같은 표현을 사용해서는 안됩니다. 대부분의 코드에서 컴파일러의 이상적인 동작은 그러한 식을 완전히 거부하는 것입니다. 당시 C 컴파일러는이를 안정적으로 감지하는 데 필요한 흐름 분석을 수행하지 않았습니다. 원래 C 표준 당시에도 여전히 흔하지는 않았습니다.

오늘날 커뮤니티에서도 수용 가능한지 잘 모르겠습니다. 많은 컴파일러가 이러한 종류의 흐름 분석을 수행 할 수 있지만 일반적으로 최적화를 요청할 때만 수행합니다. 나는 대부분의 프로그래머들이 처음부터 쓸 수없는 코드를 거부 할 수 있도록 "디버그"빌드 속도를 늦추는 아이디어를 원한다고 의심한다.

C가 한 일은 반 합리적인 두 번째 최선의 선택이다. 사람들에게 그렇게하지 말라고 컴파일러에게 코드를 거부하도록한다. 이렇게하면 사용하지 않는 사람들의 컴파일 속도가 느려지는 것을 피할 수 있지만 원하는 경우 그러한 코드를 거부하는 컴파일러를 작성할 수 있습니다 (또는 사람들이 사용하도록 선택할 수있는 플래그를 거부 할 수 있음) 또는 적합하다고 생각되는대로).

적어도 IMO,이 정의 된 행동을하는 것은 (에 적어도 가까운)이 될 것이다 최악 만들 수 결정. VLIW 스타일 하드웨어에서는 증분 연산자를 합리적으로 사용하기 위해 느린 코드를 생성 할 수 있습니다 (크래시 코드를 악용하는 크 래피 코드를 위해). 엉터리 코드를 사용하면 실제로 필요한 경우에만 느린 (직렬화 된) 코드를 생성 할 수 있습니다.

결론 :이 문제를 치료하려면 반대 방향으로 생각해야합니다. 이러한 코드의 기능을 정의하는 대신 언어를 정의하여 그러한 표현이 전혀 허용되지 않도록해야합니다 (대부분의 프로그래머는 해당 요구 사항을 강제하는 것보다 빠른 컴파일을 선택해야한다는 사실에 따라야합니다).


IMO는 대부분의 경우 느린 명령이 빠른 명령보다 속도가 훨씬 느리며 프로그램 성능에 항상 영향을 줄 것이라고 믿을 이유가 거의 없습니다. 나는 이것을 조기 최적화하에 분류 할 것입니다.
DeadMG

어쩌면 나는 뭔가를 놓치고있을 것입니다. 아무도 그런 코드를 작성하지 않아야한다면 왜 최적화에 관심이 있습니까?
ugoren 2016 년

1
@ugoren : a=b[i++];(예를 들어) 와 같은 코드를 작성 하는 것이 좋으며 최적화하는 것이 좋습니다. 그러나 합리적인 코드를 아프게하는 점은 그렇게 ++i++정의되지 않은 의미를 갖지 않습니다 .
Jerry Coffin

2
@ugoren 문제는 진단 중 하나입니다. 과 같은 표현을 완전히 허용하지 않는 유일한 목적은 ++i++일반적으로 부작용이있는 유효한 표현 (예 :)과 구별하기 어렵다는 것입니다 a=b[i++]. Dragon Book을 올바르게 기억하면 실제로 NP-hard 문제입니다. 그렇기 때문에이 행동은 금지 된 것이 아니라 UB입니다.
Konrad Rudolph

1
나는 성능이 유효한 논쟁이라고 믿지 않는다. 두 경우 모두 매우 작은 차이와 매우 빠른 실행을 고려하여이 사례가 충분히 일반적이라고 믿기 어려워합니다. 많은 프로세서와 아키텍처에서 작은 성능 저하가 눈에 띄는 것은 말할 것도 없습니다 .
DeadMG

9

C # 컴파일러 팀의 수석 디자이너 인 Eric Lippert는 자신의 블로그 에 언어 사양 수준에서 정의되지 않은 기능을 만들기 위해 선택할 수있는 여러 가지 고려 사항에 대한 기사 를 블로그에 게시했습니다 . 분명히 C #은 언어 디자인에 다른 요소를 사용하는 다른 언어이지만, 그 점은 관련이 있습니다.

특히, 그는 기존 구현이 있고위원회에 대표가있는 언어에 대해 기존 컴파일러를 갖는 문제를 지적합니다. 이것이 사실인지 확실하지 않지만 대부분의 C 및 C ++ 관련 사양 토론과 관련이 있습니다.

또한 언급했듯이 컴파일러 최적화의 성능 잠재력에 주목하십시오. 요즘 CPU 성능이 C가 어렸을 때보 다 수십 배 더 큰 것은 사실이지만, 요즘 수행되는 많은 양의 C 프로그래밍은 잠재적으로 성능 향상과 잠재적 인 (가설 적 미래) 가능성으로 인해 수행됩니다. ) CPU 명령 최적화 및 멀티 코어 처리 최적화는 부작용 및 시퀀스 포인트를 처리하기위한 규칙이 지나치게 제한되어 있기 때문에 배제하기 어리 석습니다.


링크 된 기사에서 C #이 내가 제안한 것과 멀지 않은 것 같습니다. 부작용의 순서는 "부작용을 일으키는 실에서 관찰되는 경우"로 정의됩니다. 멀티 스레딩에 대해서는 언급하지 않았지만 일반적으로 C는 다른 스레드의 관찰자에게 많은 것을 보장하지는 않습니다.
ugoren

5

먼저 정의 되지 않은 동작 의 정의 를 살펴 보겠습니다 .

3.4.3

1 이 국제 표준이 요구 사항을 부과하지 않는 이식 불가능하거나 잘못된 프로그램 구성 또는 잘못된 데이터를 사용할 때 정의
되지 않은

동작 동작 (진단 메시지 발행 여부에 관계없이) 환경의 문서화 된 방식으로 번역 또는 실행 종료 (진단 메시지 발행).

3 예 정의되지 않은 동작의 예는 정수 오버 플로우에서의 동작입니다.

다시 말해서, "정의되지 않은 행동"은 컴파일러가 원하는 방식으로 상황을 자유롭게 처리 할 수 ​​있다는 것을 의미하며, 그러한 행동은 "올바른"것으로 간주됩니다.

논의중인 문제의 근본은 다음과 같습니다.

6.5 표현

...
3 연산자와 피연산자의 그룹화는 구문으로 표시됩니다. 74) 특정한은 함수 호출 (후에 에드을 제외하고 (), &&, ||, ?:, 및 콤마 연산자) 표현식의 평가 순서 부작용이 수행되는 순서가 모두 unspeci 인터넷 ED이다 .

강조가 추가되었습니다.

다음과 같은 표현이 주어집니다.

x = a++ * --b / (c + ++d);

하위 표현식은 a++, --b, c, 그리고 ++d평가 될 수 임의의 순서로 . 또한, 부작용이 a++, --b그리고 ++d다음 시퀀스 지점 전에 언제든지 적용 할 수있다 (경우에도 IOW a++전에 평가되고 --b, 보장되지있어 a한다 갱신 전의 --b평가). 다른 사람들이 말했듯이,이 동작의 이론적 근거는 구현에 최적의 방식으로 작업을 재정렬 할 수있는 자유를 부여하는 것입니다.

그러나 이것 때문에

x = x++
y = i++ * i++
a[i] = i++
*p++ = -*p    // this one bit me just yesterday

은 다른 구현 (또는 다른 최적화 설정을 사용하거나 주변 코드 등을 기반으로 한 동일한 구현)에 대해 다른 결과를 산출 합니다.

동작은 남아 정의되지 않은 컴파일러는 그 무엇이든간에, "옳은 일을"할 의무가되도록. 위의 경우는 파악하기 쉽지만 컴파일 타임에 포착하기가 어려운 사소한 수의 사례가 있습니다.

분명히, 당신 은 평가 순서와 부작용이 적용되는 순서가 엄격하게 정의되도록 언어 설계 할 수 있으며 , C와 C ++ 정의가 야기하는 문제를 피하기 위해 Java와 C # 모두 그렇게합니다.

그렇다면 3 번의 표준 개정 후에 왜 C로 변경되지 않았습니까? 우선, 40 년 분량의 레거시 C 코드가 있으며 이러한 변경으로 인해 해당 코드가 중단되지는 않습니다. 컴파일러 작성자에게는 약간의 부담이되는데, 이러한 변경으로 인해 기존의 모든 컴파일러가 즉시 부적합하게됩니다. 모두가 상당한 재 작성을해야합니다. 최신의 빠른 CPU에서도 평가 순서를 조정하여 실제 성능 향상을 실현할 수 있습니다.


1
문제에 대한 아주 좋은 설명. 레거시 응용 프로그램을 중단하는 것에 동의하지 않습니다. 정의되지 않은 / 지정되지 않은 동작이 구현되는 방식은 표준을 변경하지 않고 컴파일러 버전간에 변경되는 경우가 있습니다. 정의 된 동작을 변경하지 않는 것이 좋습니다.
우고 렌

4

먼저 정의되지 않은 x = x ++가 아니라는 것을 이해해야합니다. 무엇을 정의하든 아무 의미가 없기 때문에 아무도 x = x ++에 관심이 없습니다. 정의되지 않은 것은 "a = b ++ a와 b가 같은 곳"과 같습니다. 즉

void f(int *a, int *b) {
    *a = (*b)++;
}
int i;
f(&i, &i);

프로세서 아키텍처 (및 예제보다 복잡한 함수 인 경우 주변 명령문에 가장 효율적인 것)에 따라 함수가 구현 될 수있는 여러 가지 방법이 있습니다. 예를 들어, 두 가지 명백한 것 :

load r1 = *b
copy r2 = r1
increment r1
store *b = r1
store *a = r2

또는

load r1 = *b
store *a = r1
increment r1
store *b = r1

더 많은 명령어와 더 많은 레지스터를 사용하는 위에 나열된 첫 번째 것은 a와 b가 다를 수없는 모든 경우에 사용해야하는 것입니다.


당신은 실제로 나의 제안이 더 많은 기계 조작을 초래하는 경우를 보여 주지만 그것은 나에게 중요하지 않은 것처럼 보입니다. 그리고 컴파일러는 여전히 자유 롭다. 내가 추가 할 수있는 유일한 요구 사항은 b이전 에 저장하는 a것이다.
우고 렌

3

유산

C가 오늘날 재창조 될 수 있다는 가정은 유지할 수 없습니다. 생산되고 매일 사용되는 C 코드 라인이 너무 많아서 플레이 도중에 게임 규칙을 변경하는 것은 잘못입니다.

물론 규칙에 따라 C + = 와 같은 새로운 언어를 발명 할 수 있습니다 . 그러나 그것은 C가 아닙니다.


2
오늘 우리가 C를 재발 명 할 수 있다고 생각하지 않습니다. 그렇다고 이러한 문제에 대해 논의 할 수는 없습니다. 그러나 내가 제안하는 것은 실제로 재발 명되지 않습니다. 표준을 업데이트 할 때 정의되지 않은 동작을 정의되거나 지정되지 않은 것으로 변환 할 수 있으며 언어는 여전히 C입니다.
ugoren

2

무언가가 정의되었다고 선언해도 기존 컴파일러가 정의를 존중하도록 변경되지는 않습니다. 많은 장소에서 명시 적 또는 암시 적으로 의존했을 수있는 가정의 경우에 특히 그렇습니다.

가정의 주요 문제는 x = x++;(컴파일러가 쉽게 확인할 수 있고 경고해야 함) 이 아니며 컴파일러가 C99에서 쉽게 알 수없는 *p1 = (*p2)++것과 동일합니다 ( p1[i] = p2[j]++;p1 및 p2가 함수의 매개 변수 인 경우) 시퀀스 포인트 사이에 p1! = p2라고 가정 할 가능성을 넓히기 위해 추가되었으므로 최적화 가능성이 중요하다고 간주되었습니다.p1 == p2restrict


내 제안이에 관한 내용을 어떻게 바꾸는 지 알 수 없습니다 p1[i]=p2[j]++. 컴파일러가 앨리어싱을 가정 할 수 없다면 아무런 문제가 없습니다. 그것이 불가능하다면, 그것은 책으로 가야합니다- p2[j]먼저 증가시키고 p1[i]나중에 저장하십시오 . 중요하지 않은 최적화 기회를 제외하고는 문제가 없습니다.
ugoren 2016 년

두 번째 문단은 첫 번째 문단과 독립된 것이 아니라 가정이 들어올 수 있고 추적하기 어려운 장소의 예입니다.
AProgrammer 2009 년

첫 번째 단락은 매우 명백한 것을 나타냅니다. 새로운 표준에 맞게 컴파일러를 변경해야합니다. 나는 이것을 표준화하고 컴파일러 작성자가 따라 할 수있는 기회가 실제로 있다고 생각하지 않습니다. 나는 그것이 논의 할 가치가 있다고 생각합니다.
ugoren 2016 년

문제는 언어 변경에 대해 컴파일러를 변경해야한다는 것이 아니라 변경이 널리 퍼져있어 찾기가 어렵다는 것입니다. 가장 실용적인 접근은 아마도 중간 형식을 변경 할 것이다에 대한 최적화 작업, 즉 척 x = x++;하지만 기록되지 않았습니다 t = x; x++; x = t;거나 x=x; x++;또는 당신이 무엇을 의미로합니다 (그러나 진단에 대해 무엇을?). 새로운 언어의 경우 부작용을 제거하십시오.
AProgrammer

컴파일러 구조에 대해 너무 많이 알지 못합니다. 모든 컴파일러를 정말로 바꾸고 싶다면 더 관심을 기울일 것입니다. 그러나 x++함수 호출 인 것처럼 시퀀스 포인트로 취급 inc_and_return_old(&x)하면 트릭을 수행 할 수 있습니다.
ugoren

-1

어떤 경우에는 이런 종류의 코드 새로운 C ++ 11 표준에서 정의되었습니다.


5
정교하게 관리?
ugoren 2016 년

나는 x = ++x이제 잘 정의되어 있다고 생각 하지만 (그렇지 않다 x = x++)
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.