a=a++의 의미론을 정의하지 못하게 하는 이유는 무엇입니까?
선을
a = a++;
는 C에서 정의되지 않은 동작입니다.제가 묻고 싶은 질문은 왜? 입니다.
제 말은, 어떤 일을 해야 하는지 일관성 있는 순서를 제공하는 것이 어려울 수도 있다는 것을 알고 있습니다.그러나 특정 컴파일러는 항상 (특정 최적화 수준에서) 한 순서 또는 다른 순서로 수행합니다.그렇다면 왜 이것이 정확히 컴파일러의 결정에 달려있는 것일까요?
확실히 말씀드리자면, 저는 이것이 디자인 결정이었는지, 그리고 만약 그렇다면 무엇이 그것을 촉발시켰는지 알고 싶습니다.아니면 하드웨어적인 제약이 있는 것은 아닐까요?
업데이트: 이 질문은 2012년 6월 18일 제 블로그의 주제였습니다.좋은 질문 감사합니다!
왜죠? 저는 이것이 디자인 결정이었는지, 그리고 만약 그렇다면 무엇이 그것을 촉발시켰는지 알고 싶습니다.
기본적으로 ANSIC 설계위원회 회의록을 요구하시는 것인데, 저는 그것들을 가지고 있지 않습니다.만약 그날 그 방에 있던 누군가가 당신의 질문에 단호하게 대답할 수 있다면, 당신은 그 방에 있던 누군가를 찾아야 할 것입니다.
하지만 좀 더 광범위한 질문에 답할 수 있습니다.
언어 디자인 위원회가 합법적인 프로그램의 행동을 떠나게 하는 요인은 무엇입니까?
*) "정의되지 않음" 또는 "implement화가 정의됨" (**)?
첫 번째 주요 요인은 다음과 같습니다. 특정 프로그램의 동작에 대해 의견이 일치하지 않는 두 가지 기존 언어 구현이 시장에서 존재합니까?FooCorp의 컴파일러가 컴파일하는 경우M(A(), B())"콜 A, 콜 B, 콜 M", 그리고 BarCorp의 컴파일러가 그것을 "콜 B, 콜 A, 콜 M"으로 컴파일하고, 둘 다 "분명히 옳은" 행동은 아니며, 언어 디자인 위원회가 "당신 둘 다 옳다"고 말하고 구현이 정의된 행동으로 만들 강력한 동기가 있습니다.특히 FooCorp와 BarCorp 모두 위원회에 대표가 있는 경우가 그렇습니다.
다음으로 중요한 요소는 다음과 같습니다. 이 기능은 다양한 구현 가능성을 자연스럽게 제시하고 있습니까?예를 들어, C#에서 컴파일러의 "쿼리 이해력" 분석 표현은 "쿼리 이해력이 없는 동등한 프로그램으로 구문 변환한 후 해당 프로그램을 정상적으로 분석"하도록 지정됩니다.구현이 달리 할 수 있는 자유는 거의 없습니다.
대조적으로, C# 사양은 다음과 같이 말합니다.foreach루프는 동등한 것으로 취급되어야 합니다.while고리 모양으로 감다try블록, 그러나 구현에 약간의 유연성을 허용합니다.C# 컴파일러는 다음과 같이 말할 수 있습니다. 예를 들어 "나는 구현하는 방법을 알고 있습니다.foreach루프 시맨틱스를 어레이에 걸쳐 보다 효율적으로 사용할 수 있습니다."라고 설명하고 어레이를 사양대로 시퀀스로 변환하는 대신 어레이의 인덱싱 기능을 사용합니다.
세 번째 요인은 기능이 너무 복잡하여 정확한 동작을 자세히 분석하는 것이 어렵거나 비용이 많이 든다는 것입니다.C# 사양은 익명 메소드, 람다 표현식, 표현식 트리, 동적 호출, 반복자 블록 및 비동기 블록을 구현하는 방법에 대해 거의 설명하지 않습니다. 단지 원하는 의미론과 동작에 대한 일부 제한을 설명하고 나머지는 구현에 맡깁니다.
네 번째 요소는 다음과 같습니다. 기능이 분석해야 할 컴파일러에게 높은 부담을 주나요?예를 들어, C#에서 다음을 가질 경우:
Func<int, int> f1 = (int x)=>x + 1;
Func<int, int> f2 = (int x)=>x + 1;
bool b = object.ReferenceEquals(f1, f2);
b가 참일 것을 요구한다고 가정해 보겠습니다.두 함수가 "동일한" 경우를 어떻게 판단할 것입니까?함수들이 같은 내용을 가지고 있을까요? 그리고 "확장성" 분석을 하는 것은 어렵습니다. 같은 입력이 주어졌을 때 함수들이 같은 결과를 가지고 있을까요? 더 어렵습니다.언어 명세 위원회는 실행 팀이 해결해야 하는 개방형 연구 문제의 수를 최소화하기 위해 노력해야 합니다!
따라서 C#에서는 이를 구현 정의하도록 합니다. 컴파일러는 자신의 재량에 따라 참조를 동등하게 할 수도 있고 그렇지 않을 수도 있습니다.
다섯 번째 요소는 기능이 런타임 환경에 높은 부담을 주나요?
예를 들어 C# 역참조에서는 배열의 끝을 지나 잘 정의됩니다. 즉, 배열-인덱스-아웃-오브-바운드 예외가 생성됩니다.이 기능은 실행 시 0이 아니라 적은 비용으로 구현할 수 있습니다.null 수신기를 사용하여 인스턴스 또는 가상 메서드를 호출하는 것은 null-was-referenced 예외를 생성하는 것으로 정의됩니다. 이는 다시 적은 비용이지만 0이 아닌 비용으로 구현할 수 있습니다.정의되지 않은 동작을 제거하는 이점은 적은 런타임 비용을 보상합니다.
여섯 번째 요소는 다음과 같습니다. 행동을 정의하는 것이 주요 최적화를 방해합니까?예를 들어, C#은 부작용을 유발하는 스레드에서 관찰할 때 부작용의 순서를 정의합니다.그러나 다른 스레드에서 한 스레드의 부작용을 관찰하는 프로그램의 동작은 몇 가지 "특별한" 부작용을 제외하고는 구현이 정의됩니다.(휘발성 쓰기, 잠금 입력 등)C# 언어로 모든 스레드가 동일한 순서로 동일한 부작용을 관찰해야 한다면 현대적인 프로세서가 작업을 효율적으로 수행하는 것을 제한해야 할 것입니다. 현대적인 프로세서는 높은 수준의 성능을 얻기 위해 순서가 맞지 않는 실행과 정교한 캐싱 전략에 의존합니다.
이러한 요소들은 단지 몇 가지 생각나는 요소들일 뿐입니다. 물론 언어 디자인 위원회가 "구현 정의된" 또는 "정의되지 않은" 특징을 만들기 전에 토론하는 많은 다른 요소들이 있습니다.
이제 구체적인 예로 돌아가겠습니다.
C# 언어는 해당 동작을 엄격하게 정의합니다(†하는 것으로 ; 과제의 부작용 이전에 증가의 부작용이 발생하는 것으로 관찰됩니다.따라서 행동을 선택하고 그것을 고수하는 것이 가능하기 때문에 "글쎄요, 그것은 불가능합니다"라는 논쟁은 있을 수 없습니다.그렇다고 해서 최적화를 위한 주요 기회가 배제되는 것도 아닙니다.그리고 가능한 복잡한 구현 전략이 많이 존재하지 않습니다.
따라서 제 추측과 강조하지만, 이것은 추측입니다. C 언어 위원회가 부작용을 구현 정의된 행동으로 순서를 정했기 때문입니다. 시장에서 여러 컴파일러가 다르게 실행했고, 어느 컴파일러도 분명히 "더 정확하지" 않았으며, 위원회는 그 중 절반이 틀렸다고 말하기를 꺼려했기 때문입니다.
(*사용하기도 합니다 또는 때로는 컴파일러도 있습니다!하지만 그 요소는 무시해 버립시다.
(**) "정의되지 않은" 동작은 코드가 하드 디스크를 지우는 것을 포함하여 무엇이든 할 수 있음을 의미합니다.컴파일러는 특정 동작을 갖는 코드를 생성할 필요가 없으며, 정의되지 않은 동작을 갖는 코드를 생성한다는 것을 알려줄 필요도 없습니다."구현 정의된" 동작은 컴파일러 작성자에게 구현 전략을 선택할 수 있는 상당한 자유가 주어지지만 전략을 선택하고 일관성 있게 사용하며 그 선택을 문서화해야 함을 의미합니다.
(† 단 단 하나의 실에서 관찰하면 물론입니다.
컴파일러는 코드를 그렇게 쓸 이유가 없기 때문에 정의되지 않으며, 가짜 코드에 대한 특정 동작을 요구하지 않기 때문에 잘 쓰여진 코드를 보다 적극적으로 최적화할 수 있습니다.예를들면,*p = i++다음의 경우 충돌을 유발하는 방식으로 최적화될 수 있습니다.p우연히 …을 가리키다i, 아마도 두 개의 코어가 동시에 같은 메모리 위치에 쓰기 때문일 것입니다.이것이 또한 특정한 경우에 정의되지 않는다는 사실은*p다음과 같이 명시적으로 작성됩니다.i,갖기 위해i = i++, 논리적으로 따르게 됩니다.
몇 가지 예외를 제외하고 표현식을 평가하는 순서는 지정되지 않았습니다. 이는 의도적인 설계 결정이었으며, 평가 순서를 작성된 내용에서 재배치하여 보다 효율적인 기계 코드를 생성할 수 있습니다.마찬가지로, 다음과 같은 부작용이 발생하는 순서.++그리고.--적용되는 것은 다음 시퀀스 시점 이전에 발생해야 하는 요구 사항 이상으로 특정되지 않으며, 최적의 방식으로 작업을 배치할 수 있는 자유를 구현에 제공합니다.
불행하게도, 이것은 다음과 같은 표현의 결과를 의미합니다.a = a++컴파일러, 컴파일러 설정, 주변 코드 등에 따라 달라질 것입니다.이 동작은 컴파일러 구현자가 이러한 사례를 감지하고 진단을 내릴 때 걱정할 필요가 없도록 언어 표준에서 정의되지 않은 것으로 구체적으로 호출됩니다.다음과 같은 경우.a = a++분명하지만, 그와 비슷한 것은 어떻습니까?
void foo(int *a, int *b)
{
*a = (*b)++;
}
파일에 있는 기능이 그것뿐인 경우(또는 발신자가 다른 파일에 있는 경우) 컴파일 시 여부를 알 수 없습니다.a그리고.b같은 대상을 가리켜라; 당신은 무엇을 합니까?
모든 표현식을 특정 순서로 평가하고 모든 부작용을 평가의 특정 지점에 적용하도록 명령할 수 있습니다. 이는 Java와 C#가 수행하는 작업이며, 이러한 언어의 표현식은 다음과 같습니다.a = a++항상 잘 정의되어 있습니다.
애매하지만 구문적으로는 틀리지 않습니다.무엇을 해야 합니까?a그래요? 둘다=그리고.++" '타이밍' 그래서 두 개의 정의 중 되지 않은 로 두었습니다.따라서 임의의 순서를 정의하는 대신 두 연산자 정의 중 하나와 충돌하기 때문에 정의되지 않은 상태로 남겨졌습니다.
포스트픽스++operator가 증분 이전의 값을 반환합니다.그래서, 첫번째 단계에서,a이전 가치에 할당됩니다. (그렇습니다.)++반환).두 작업이 동일한 개체에 적용되기 때문에 다음 시점에서 증분 또는 할당이 먼저 수행될 것인지 정의되지 않습니다.a), 그리고 언어는 이들 연산자의 평가 순서에 대해 아무것도 언급하지 않습니다.
누군가가 다른 이유를 제시할 수도 있지만, 최적화(어셈블리 표현이 더 나은) 관점에서 보면,aCPU 레지스터에 로드해야 하며, 포스트픽스 연산자의 값은 다른 레지스터 또는 동일한 레지스터에 배치해야 합니다.
따라서 마지막 할당은 레지스터 한 개 또는 두 개를 사용하는 옵티마이저에 의존할 수 있습니다.
시퀀스 포인트가 개입되지 않고 동일한 개체를 두 번 업데이트하는 것은 정의되지 않은 동작입니다...
- 그것이 컴파일러 작가들을 더 행복하게 해주기 때문입니다.
- 구현을 통해 어떻게든 정의할 수 있기 때문입니다.
- 그것이 필요하지 않을 때 특정한 제약을 강요하지 않기 때문입니다.
가정하다a는 값이 0x0001FFFF인 포인터입니다.그리고 컴파일러가 높은 부분과 낮은 부분 사이에 캐리(carry)를 포함하여 증가분을 따로 적용해야 하는 구조를 분할했다고 가정합니다.옵티마이저는 저장된 최종 값이 0x0002가 되도록 쓰기 순서를 바꿀 수 있습니다.FFFF 즉, 증가 전의 낮은 부분과 증가 후의 높은 부분입니다.
이 값은 예상했던 값의 두 배입니다.응용 프로그램이 소유하지 않은 메모리를 가리킬 수도 있고 (일반적으로) 트래핑 표현일 수도 있습니다.즉, CPU는 이 값이 레지스터에 로드되는 즉시 하드웨어 오류를 발생시켜 애플리케이션을 손상시킬 수 있습니다.즉각적인 충돌을 일으키지 않더라도 응용프로그램이 사용하는 것은 매우 잘못된 값입니다.
같은 종류의 일이 다른 기본적인 유형에서도 일어날 수 있고, C 언어는 심지어 int도 트래핑 표현을 가질 수 있게 해줍니다.C는 광범위한 하드웨어에서 효율적인 구현이 가능하도록 노력합니다.8086과 같은 세분화된 기계에서 효율적인 코드를 얻는 것은 어렵습니다.이러한 정의되지 않은 행동을 함으로써 언어 구현자는 공격적으로 최적화할 수 있는 자유를 조금 더 가질 수 있습니다.그것이 실제로 성과에 차이를 가져온 적이 있는지는 모르겠지만, 언어 위원회는 분명히 옵티마이저에게 모든 혜택을 주고 싶어했습니다.
언급URL : https://stackoverflow.com/questions/9943697/whats-the-reason-for-letting-the-semantics-of-a-a-be-undefined
'programing' 카테고리의 다른 글
| 워드프레스 - 연락처 양식 7 - 연락처 양식을 보내다가 고착됨 (0) | 2023.10.29 |
|---|---|
| 정수 배열에서 고유 난수 생성 (0) | 2023.10.29 |
| 실행 수를 많이 사용하여 파이썬의 사전 목록을 MySQL에 삽입하려면 어떻게 해야 합니까? (0) | 2023.10.29 |
| 쿼리 결과에 따라 간격을 두고 날짜를 "스텝"하거나 "스킵"할 수 있는 기능을 갖춘 Mysql 쿼리 (0) | 2023.10.29 |
| AF_의 차이점은 무엇입니까?INET 및 PF_INET 상수? (0) | 2023.10.29 |