C 및 C ++에서 정의되지 않은 동작은 무엇입니까? 지정되지 않은 동작 및 구현 정의 동작은 어떻습니까? 그들 사이의 차이점은 무엇입니까?
C 및 C ++에서 정의되지 않은 동작은 무엇입니까? 지정되지 않은 동작 및 구현 정의 동작은 어떻습니까? 그들 사이의 차이점은 무엇입니까?
답변:
정의되지 않은 동작 은 다른 언어에서 온 프로그래머들에게 놀라운 C 및 C ++ 언어 측면 중 하나입니다 (다른 언어에서는 더 잘 숨기려고합니다). 기본적으로 많은 C ++ 컴파일러가 프로그램의 오류를보고하지 않더라도 예측 가능한 방식으로 동작하지 않는 C ++ 프로그램을 작성할 수 있습니다!
고전적인 예를 보자.
#include <iostream>
int main()
{
char* p = "hello!\n"; // yes I know, deprecated conversion
p[0] = 'y';
p[5] = 'w';
std::cout << p;
}
변수 p
는 문자열 literal을 가리키고 "hello!\n"
아래 두 할당은 해당 문자열 리터럴을 수정하려고합니다. 이 프로그램은 무엇을합니까? C ++ 표준의 2.14.5 단락 11에 따르면 정의되지 않은 동작을 호출합니다 .
문자열 리터럴을 수정하려는 결과는 정의되지 않습니다.
사람들이 비명을 지르는 것을들을 수 있습니다. "잠깐만,이 문제를 컴파일하지 않고 결과를 얻을 수 있습니다. yellow
마세요. "또는 "정의되지 않은 문자열 리터럴이 읽기 전용 메모리에 저장되어 있으므로 첫 번째 할당 시도로 인해 코어 덤프가 발생합니다." 이것은 정의되지 않은 동작의 문제입니다. 기본적으로이 표준은 정의되지 않은 동작 (비강 악마 포함)을 호출하면 어떤 일이든 발생할 수 있습니다. 언어의 당신의 정신 모델에 따라 "올바른"행동이 있다면, 그 모델은 단순히 잘못된 것입니다; C ++ 표준은 유일한 투표 기간을 갖습니다.
정의되지 않은 동작의 다른 예로는 경계를 넘어 배열에 액세스하거나 , 널 포인터를 역 참조하거나 , 수명이 끝난 후 오브젝트 에 액세스 하거나, 영리한 식을 쓰는 등 이 i++ + ++i
있습니다.
C ++ 표준의 섹션 1.9에는 정의되지 않은 동작의 덜 위험한 두 형제, 지정되지 않은 동작 및 구현 정의 된 동작 도 언급 되어 있습니다 .
이 국제 표준의 의미 론적 설명은 매개 변수화 된 비결정론 적 추상 기계를 정의합니다.
추상 기계의 특정 측면과 작동은이 국제 표준에서 구현 정의 (예 :)로 설명됩니다
sizeof(int)
. 이것들은 추상 기계의 매개 변수를 구성합니다. 각 구현에는 이러한 점에서 특성과 동작을 설명하는 문서가 포함되어야합니다.추상 기계의 어떤 다른 측면들 및 동작들은이 국제 표준에서 지정되지 않은 것으로 기술 되어있다 (예를 들어, 함수에 대한 인수의 평가 순서). 가능한 경우이 국제 표준은 허용 가능한 동작 집합을 정의합니다. 이것들은 추상 기계의 비 결정적 측면을 정의합니다.
다른 국제 오퍼레이션은이 국제 표준에서 정의되지 않은 것으로 설명됩니다 (예 : 널 포인터 역 참조의 영향). [ 참고 : 이 국제 표준은 정의되지 않은 동작을 포함하는 프로그램의 동작에 대한 요구 사항을 부과하지 않습니다. — 끝 참고 ]
특히 1.3.24 절에는 다음과 같이 명시되어 있습니다.
허용되지 않는 정의 된 행동은 예측할 수없는 결과로 상황을 완전히 무시하는 것에서부터 환경의 특성화 된 문서화 된 방식으로 진단 또는 프로그램 실행 중 동작 (진단 메시지 발행 여부에 관계없이), 번역 또는 실행 종료 (발급 포함)에 이르기까지 다양 합니다. 진단 메시지).
정의되지 않은 동작을 피하기 위해 무엇을 할 수 있습니까? 기본적으로, 당신은 그들이 무엇을 말하는지 알고있는 저자에 의해 좋은 C ++ 책 을 읽어야 합니다. 인터넷 자습서를 조이십시오. 스크류 불스 어린이.
int f(){int a; return a;}
: a
함수 호출간에 값 이 변경 될 수 있습니다.
글쎄, 이것은 기본적으로 표준에서 직접 복사하여 붙여 넣습니다.
3.4.1 1 구현-정의 된 행동 지정되지 않은 행동 - 각 구현이 선택 방법을 문서화 함
2 예 구현 정의 된 동작의 예는 부호있는 정수가 오른쪽으로 이동 될 때 상위 비트의 전파입니다.
3.4.3 1 비 이동식 또는 잘못된 프로그램 구성 또는 잘못된 데이터 사용시 정의되지 않은 행동 거동
2 참고 가능한 정의되지 않은 행동은 예측할 수없는 결과로 상황을 완전히 무시하는 것부터, 환경의 특성화 된 문서화 된 방식으로 (진단 메시지 발행 여부에 관계없이) 번역 또는 프로그램 실행 중 행동, 번역 또는 실행 종료 진단 메시지 발행).
3 예 정의되지 않은 동작의 예는 정수 오버플로에서의 동작입니다.
3.4.4 1 불특정 행동 불특정 가치의 사용, 또는이 표준이 두 가지 이상의 가능성을 제공하고 어떠한 경우에도 더 이상 요구되지 않는 다른 행동
2 예 지정되지 않은 동작의 예는 함수에 대한 인수가 평가되는 순서입니다.
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
정의되지 않은 동작 호출하는 미사일을 발사하지 않는 함수를 호출하는 모든 수단 때문에,이 호출 할 수 있음을 확인할 수 있습니다 컴파일러 launch_missiles()
무조건입니다.
표준에 대한 엄격한 정의보다 이해하기 쉬운 표현이 더 쉬울 수 있습니다.
구현 정의 동작
언어에는 데이터 유형이 있다고 말합니다. 컴파일러 공급 업체는 사용할 크기를 지정하고 수행 한 작업에 대한 문서를 제공합니다.
정의되지 않은 동작
문제가 있습니다. 예를 들어에 int
맞지 않는 값이 매우 큽니다 .char
. 그 가치를 char
어떻게 넣 습니까? 실제로 방법은 없습니다! 어떤 일이든 일어날 수 있지만 가장 현명한 것은 int의 첫 번째 바이트를 가져 와서 넣는 것입니다 char
. 첫 번째 바이트를 할당하는 것은 잘못된 일이지만 그 결과는 후드 아래에서 발생합니다.
불특정 행동
이 두 기능 중 어떤 기능이 먼저 실행됩니까?
void fun(int n, int m);
int fun1()
{
cout << "fun1";
return 1;
}
int fun2()
{
cout << "fun2";
return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?
언어는 왼쪽에서 오른쪽으로 또는 오른쪽에서 왼쪽으로 평가를 지정하지 않습니다! 따라서 지정되지 않은 동작은 정의되지 않은 동작을 초래하거나 발생하지 않을 수 있지만 프로그램에서 지정되지 않은 동작을 생성해서는 안됩니다.
@ eSKay 나는 당신의 질문이 더 명확하게 답변을 편집 할 가치가 있다고 생각합니다 :)
...에 대한
fun(fun1(), fun2());
행동 "구현 정의 된"아닌가요? 컴파일러는 결국 하나 또는 다른 코스를 선택해야합니까?
구현 정의와 지정되지 않은 차이점은 컴파일러가 첫 번째 경우 동작을 선택해야하지만 두 번째 경우에는 필요하지 않다는 것입니다. 예를 들어, 구현에는의 정의가 하나만 있어야합니다 sizeof(int)
. 따라서 sizeof(int)
프로그램의 일부에서는 4이고 다른 것은 8 이라고 말할 수 없습니다 . 컴파일러가 OK라고 말할 수있는 지정되지 않은 동작과 달리이 인수를 왼쪽에서 오른쪽으로 평가하고 다음 함수의 인수를 오른쪽에서 왼쪽으로 평가합니다. 동일한 프로그램에서 발생할 수 있으므로 unspecified 라고 합니다. 실제로 지정되지 않은 동작 중 일부를 지정하면 C ++가 더 쉬워 질 수 있습니다. Stroustrup 박사의 답변을 여기에서 살펴보십시오. .
컴파일러에게 이러한 자유를 제공하고 "일반적인 왼쪽에서 오른쪽 평가"를 요구하는 것의 차이는 중요 할 수 있다고 주장됩니다. 나는 확신 할 수 없지만 자유를 활용하는 무수한 컴파일러가 있고 자유를 열정적으로 지키는 일부 사람들이 있다면 변화가 어려울 수 있으며 C와 C ++ 세계의 먼 구석에 침투하는 데 수십 년이 걸릴 수 있습니다. 모든 컴파일러가 ++ i + i ++와 같은 코드에 대해 경고하는 것은 아닙니다. 마찬가지로, 인수 평가 순서는 지정되어 있지 않습니다.
IMO가 너무 많은 "사물"은 정의되지 않은, 지정되지 않은, 구현 정의 된 등으로 남아 있습니다. 그러나 그것은 말하기 쉽고 예를 제시하기는 어렵지만 수정하기는 어렵습니다. 또한 대부분의 문제를 피하고 이식 가능한 코드를 생성하는 것이 어려운 것은 아닙니다.
fun(fun1(), fun2());
동작은하지 않습니다 "implementation defined"
? 컴파일러는 결국 하나 또는 다른 코스를 선택해야합니까?
"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
나는 이것이 can
일어나는 것을 이해합니다 . 요즘 우리가 사용하는 컴파일러를 사용합니까?
공식 C 근거 문서에서
지정되지 않은 동작, 정의되지 않은 동작 및 구현 정의 된 동작 이라는 용어 는 표준 속성이 완전히 설명 할 수 없거나 완전히 설명 할 수없는 프로그램 작성 결과를 분류하는 데 사용됩니다. 이 분류를 채택하는 목적은 표준에 따른 적합성을 제거하지 않고도 구현 품질을 시장에서 적극적으로 강화할 수있을뿐만 아니라 시장에서 활발한 힘을 발휘할 수 있도록하는 다양한 구현을 허용하는 것입니다. 표준에 대한 부록 F는 이러한 세 가지 범주 중 하나에 해당하는 행동을 정리합니다.
지정되지 않은 동작 은 구현 자에게 프로그램을 번역 할 때 위도를 제공합니다. 이 위도는 프로그램을 번역하지 못하는 한 확장되지 않습니다.
정의되지 않은 동작 은 구현 자에게 진단하기 어려운 특정 프로그램 오류를 포착하지 못하도록 라이센스를 부여합니다. 또한 가능한 언어 확장이 가능한 영역을 식별합니다. 구현자는 공식적으로 정의되지 않은 동작의 정의를 제공하여 언어를 보강 할 수 있습니다.
구현 정의 동작은 구현 자 에게 적절한 접근 방식을 선택할 수있는 자유를 제공하지만이 선택은 사용자에게 설명해야합니다. 구현 정의로 지정된 동작은 일반적으로 사용자가 구현 정의에 따라 의미있는 코딩 결정을 내릴 수있는 동작입니다. 구현자는 구현 정의의 범위를 결정할 때이 기준을 명심해야합니다. 지정되지 않은 동작과 마찬가지로 구현 정의 동작을 포함하는 소스를 변환하지 못하는 것만으로는 적절하지 않습니다.
정의되지 않은 동작과 지정되지 않은 동작 에 대한 간단한 설명이 있습니다.
그들의 최종 요약 :
요약하면, 지정되지 않은 동작은 일반적으로 소프트웨어를 이식 할 수있는 경우가 아니라면 걱정하지 않아도됩니다. 반대로, 정의되지 않은 동작은 항상 바람직하지 않으며 절대 발생하지 않아야합니다.
역사적으로, 구현 정의 행동과 정의되지 않은 행동은 표준 구현 자들이 품질 구현을 작성하는 사람들이 판단을 사용하여 행동 보장이있을 경우 어떤 행동 보장이 응용 프로그램에서 실행되는 의도 된 응용 분야의 프로그램에 도움이 될지를 결정할 것으로 예상하는 상황을 나타 냈습니다. 의도 된 목표. 하이 엔드 숫자 처리 코드의 요구는 저수준 시스템 코드의 요구와는 매우 다르며, UB와 IDB는 컴파일러 작성자에게 다양한 요구를 충족시킬 수있는 유연성을 제공합니다. 범주는 구현이 특정 목적이나 어떤 목적에도 유용한 방식으로 작동하도록 요구하지 않습니다. 그러나 특정 목적에 적합하다고 주장하는 품질 구현은 그러한 목적에 맞는 방식으로 행동해야합니다표준이 요구하는지의 여부 .
구현-정의 된 행동과 정의되지 않은 행동의 유일한 차이점은 전자가 구현 이 도움이 될 수없는 경우에도 일관된 행동을 정의하고 문서화해야한다는 점 이다 . 이들 사이의 구분선은 일반적으로 구현에서 동작을 정의하는 것이 유용한 지 여부 (컴파일러 작성자가 표준에서 요구하는지 여부에 관계없이 유용한 동작을 정의해야 함)가 아니라 동작 정의가 동시에 비용이 많이 드는 구현이 있는지 여부입니다. 쓸모없는 . 이러한 구현이 존재할 수 있다는 판단은 어떤 방식, 형태 또는 형태로도 다른 플랫폼에서 정의 된 동작을 지원하는 데 유용하다는 판단을 내포하지 않습니다.
불행하게도, 1990 년대 중반부터 컴파일러 제작자들은 행동 보장의 부족이 행동 보장이 중요하지 않은 응용 분야와 실질적으로 비용이 들지 않는 시스템에서도 비용 가치가 없다는 판단으로 해석하기 시작했습니다. UB를 합리적인 판단의 초청으로 취급하는 대신 컴파일러 작성자는 UB 를 그렇게하지 않는 변명으로 취급하기 시작했습니다 .
예를 들어 다음 코드가 제공됩니다.
int scaled_velocity(int v, unsigned char pow)
{
if (v > 250)
v = 250;
if (v < -250)
v = -250;
return v << pow;
}
2의 보수 구현 은 긍정적이든 부정적 v << pow
이든 상관없이 표현 을 2의 보수 교대로 취급하기 위해 어떠한 노력도 들일 필요가 없습니다 v
.
그러나 오늘날의 컴파일러 작성자 중 선호되는 철학 v
은 프로그램이 정의되지 않은 동작에 관여하는 경우에만 음수 일 수 있기 때문에 프로그램의 음수 범위를 음수 범위로 지정할 이유가 없습니다 v
. 음수 값의 왼쪽 이동이 모든 단일 컴파일러에서 지원되는 데 사용되었지만 기존 코드의 상당수가 해당 동작에 의존하지만 현대 철학은 왼쪽 이동 음수 값이 다음과 같이 UB라는 사실을 표준에서 해석합니다. 컴파일러 작성자가이를 무시해도된다는 것을 암시합니다.
<<
음수에 UB 라는 사실 은 불쾌한 작은 함정이며 그 사실을 기억하게되어 기쁩니다!
i+j>k
추가 부작용이없는 경우 프로그래머가 1 또는 0을 생성 하는지 여부를 신경 쓰지 않으면 다른 부작용이 없다면 컴파일러는 프로그래머가 코드를 다음과 같이 작성하면 불가능한 대규모 최적화를 수행 할 수 있습니다 (int)((unsigned)i+j) > k
.
C ++ 표준 n3337 § 1.3.10 구현 정의 동작
구현 및 각 구현 문서에 따라 올바르게 구성된 프로그램 구성 및 올바른 데이터에 대한 동작
때로는 C ++ Standard는 일부 구문에 특정 동작을 부과하지 않지만 대신 특정 구현 (라이브러리 버전)에 의해 잘 정의 된 특정 동작을 선택하고 설명 해야한다고 말합니다 . 따라서 표준에서 설명하지 않아도 사용자는 프로그램이 어떻게 작동하는지 정확하게 알 수 있습니다.
C ++ 표준 n3337 § 1.3.24 정의되지 않은 동작
이 국제 표준이 요구 사항을 부과하지 않는 행동 [참고 :이 국제 표준이 명시적인 행동 정의를 생략하거나 프로그램이 잘못된 구성 또는 잘못된 데이터를 사용하는 경우 정의되지 않은 동작이 예상 될 수 있습니다. 허용되지 않는 정의 된 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것부터, 환경의 특성화 된 문서화 된 방식으로 진단 또는 프로그램 실행 중 (진단 메시지 발행 여부에 관계없이), 번역 또는 실행 종료 (발급 포함)에 이르기까지 다양합니다. 진단 메시지). 많은 잘못된 프로그램 구성은 정의되지 않은 동작을 유발하지 않습니다. 그들은 진단을 받아야합니다. — 끝 참고]
프로그램에서 C ++ 표준에 따라 정의되지 않은 구문을 발견하면 원하는대로 수행 할 수 있습니다 (이메일을 보내거나 이메일을 보내거나 코드를 완전히 무시할 수 있음).
C ++ 표준 n3337 § 1.3.25 지정되지 않은 동작
구현에 의존하는 올바른 형식의 프로그램 구성 및 올바른 데이터에 대한 동작 [참고 : 구현은 어떤 동작이 발생하는지 문서화 할 필요가 없습니다. 가능한 행동의 범위는 일반적으로이 국제 표준에 의해 묘사됩니다. — 끝 참고]
C ++ Standard는 일부 구문에 특정 동작을 부과하지 않지만 대신 특정 구현 (라이브러리 버전)에 의해 잘 정의 된 특정 동작을 선택해야한다고 말합니다 ( 봇은 필요하지 않음 ). 따라서 설명이 제공되지 않은 경우 사용자는 프로그램의 작동 방식을 정확히 이해하기가 어려울 수 있습니다.
구현 정의
구현자는 원하는 문서를 잘 작성해야하며 표준은 선택을 제공하지만 반드시 컴파일해야합니다.
불특정-
구현 정의와 동일하지만 문서화되지 않음
찾으시는 주소가 없습니다-
어떤 일이든 일어날 수 있습니다.
uint32_t s;
평가 1u<<s
하면 s
0을 산출하거나 2를 산출 할 것으로 예상되지만 별다른 것은하지 않습니다. 그러나 최신 컴파일러를 평가 1u<<s
하면 컴파일러 s
는 사전에 32 개 미만이어야 했기 때문에 s
32 개 이상인 경우에만 해당되는 식의 전후에있는 코드를 생략 할 수 있다고 컴파일러가 판단 할 수 있습니다.