정의되지 않은 동작 (이 예에서는 0으로 나누기)을 호출하는 코드는 실행되지 않습니다. 프로그램이 여전히 정의되지 않은 동작입니까?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
여전히 정의되지 않은 행동이라고 생각하지만 표준에서 나를지지하거나 거부 할 증거를 찾을 수 없습니다.
그래서, 어떤 아이디어?
정의되지 않은 동작 (이 예에서는 0으로 나누기)을 호출하는 코드는 실행되지 않습니다. 프로그램이 여전히 정의되지 않은 동작입니까?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
여전히 정의되지 않은 행동이라고 생각하지만 표준에서 나를지지하거나 거부 할 증거를 찾을 수 없습니다.
그래서, 어떤 아이디어?
답변:
C 표준이 "행동"및 "정의되지 않은 동작"이라는 용어를 정의하는 방법을 살펴 보겠습니다.
참조는 ISO C 2011 표준 의 N1570 초안에 대한 것입니다. 발표 된 세 가지 ISO C 표준 (1990, 1999, 2011)에서 관련한 차이점을 알지 못합니다.
섹션 3.4 :
행동
외관 또는 행동
좋아, 그것은 약간 모호하지만 주어진 진술은 실제로 실행되지 않는 한 "외관"이없고 확실히 "조치"가 없다고 주장한다.
섹션 3.4.3 :
이식 할 수 없거나 오류가있는 프로그램 구성 또는 잘못된 데이터 사용시 정의되지 않은 동작 동작 (이 국제 표준이 요구 사항을 부과하지 않음)
그것은 그러한 구조의 " 사용시 " 라고 말한다 . "사용"이라는 단어는 표준에 정의되어 있지 않으므로 일반적인 영어 의미로 되돌아갑니다. 구성은 실행되지 않으면 "사용"되지 않습니다.
그 정의 아래에 메모가 있습니다.
참고 가능한 정의되지 않은 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것, 번역 또는 프로그램 실행 중에 환경 특성 (진단 메시지 발행 여부에 관계없이)의 문서화 된 방식으로 동작하는 것, 번역 또는 실행 종료 ( 진단 메시지 발행).
따라서 컴파일러는 동작이 정의되지 않은 경우 컴파일 타임에 프로그램을 거부 할 수 있습니다 . 그러나 그것에 대한 나의 해석은 프로그램의 모든 실행이 정의되지 않은 동작을 만날 것이라는 것을 증명할 수 있을 때만 그렇게 할 수 있다는 것입니다. 내 생각에 이것은 다음을 의미합니다.
if (rand() % 2 == 0) {
i = i / 0;
}
확실히 정의되지 않은 동작을 가질 수 있으며 컴파일 타임에 거부 될 수 없습니다.
실질적으로 프로그램은 정의되지 않은 동작을 호출하는 것을 방지하기 위해 런타임 테스트를 수행 할 수 있어야하며 표준은이를 허용해야합니다.
귀하의 예는 다음과 같습니다.
if (0) {
i = 1/0;
}
0으로 나누기를 실행하지 않습니다. 매우 일반적인 관용구는 다음과 같습니다.
int x, y;
/* set values for x and y */
if (y != 0) {
x = x / y;
}
부서는 확실히 정의되지 않은 동작을 가지고 y == 0
있지만 y == 0
. 동작은 잘 정의되어 있으며, 예제가 잘 정의 된 것과 같은 이유로 잠재적 인 정의되지 않은 동작은 실제로 발생할 수 없기 때문 입니다.
( INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(예, 정수 나눗셈이 오버플로 될 수있는 경우가 아니라면 ) 이는 별도의 문제입니다.)
(삭제 이후) 주석에서 누군가 컴파일러가 컴파일 타임에 상수 표현식을 평가할 수 있다고 지적했습니다. 사실이지만이 경우에는 관련이 없습니다.
i = 1/0;
1/0
상수 표현식이 아닙니다 .
일정 표현식 으로 감소 통사 범주이다 조건부 표현식 (제외되는 할당 및 콤마 식). 생산 상수 표현식 은 케이스 레이블과 같이 실제로 상수 표현식이 필요한 컨텍스트 에서만 문법에 나타납니다 . 따라서 다음과 같이 작성하면 :
switch (...) {
case 1/0:
...
}
그 다음 1/0
은 상수 표현식이며 6.6p4의 제약 조건을 위반하는 표현식입니다. "각 상수 표현식은 해당 유형에 대해 표현 가능한 값 범위에있는 상수로 평가되어야합니다.", 따라서 진단이 필요합니다. 그러나 할당의 오른쪽에는 constant-expression이 필요하지 않고 단지 conditional-expression 이 필요하므로 상수 식에 대한 제약 조건이 적용되지 않습니다. 컴파일러는 컴파일 타임에 사용할 수있는 모든 표현식을 평가할 수 있지만, 동작이 실행 중에 평가 된 것과 동일한 경우 (또는의 컨텍스트 에서 execution () 중에 평가 되지 않은if (0)
경우 에만 ).
(정확히 상수 표현식 과 똑같은 것이 반드시 상수 표현식 일 필요는 없습니다 x + y * z
.에서 시퀀스 x + y
가 나타나는 컨텍스트 때문에 추가 표현식 이 아닙니다 .)
이는 제가 인용하려고했던 N1570 섹션 6.6의 각주를 의미합니다.
따라서 다음 초기화
static int i = 2 || 1 / 0;
에서 식은 값이 1 인 유효한 정수 상수 식입니다.
실제로이 질문과 관련이 없습니다.
마지막으로, 실행 중에 일어나는 일이 아닌 정의되지 않은 동작을 유발하도록 정의 된 몇 가지 사항이 있습니다. C 표준의 Annex J, 섹션 2 (다시 N1570 초안 참조 )에는 정의되지 않은 동작을 유발하는 항목이 나열되어 있으며 나머지 표준에서 수집되었습니다. 몇 가지 예 (완전한 목록이라고 주장하지 않음)는 다음과 같습니다.
- 비어 있지 않은 소스 파일은 백 슬래시 문자가 바로 앞에 나오지 않거나 부분 전처리 토큰 또는 주석으로 끝나는 개행 문자로 끝나지 않습니다.
- 토큰 연결은 범용 문자 이름의 구문과 일치하는 문자 시퀀스를 생성합니다.
- 기본 소스 문자 세트에없는 문자가 소스 파일에서 발견되었습니다. 단, 식별자, 문자 상수, 문자열 리터럴, 헤더 이름, 주석 또는 토큰으로 변환되지 않는 전처리 토큰은 예외입니다.
- 식별자, 주석, 문자열 리터럴, 문자 상수 또는 헤더 이름에 잘못된 멀티 바이트 문자가 포함되어 있거나 초기 시프트 상태에서 시작 및 종료되지 않습니다.
- 동일한 식별자가 동일한 번역 단위에 내부 및 외부 연결을 모두 포함합니다.
이러한 특정 경우는 컴파일러 가 감지 할 수 있는 것입니다. 위원회가 모든 구현에 동일한 행동을 부과하기를 원치 않았거나 부과 할 수 없었기 때문에 그들의 행동이 정의되지 않았다고 생각합니다. 그리고 허용되는 행동 범위를 정의하는 것은 노력할 가치가 없었습니다. 그것들은 실제로 "실행되지 않을 코드"의 범주에 속하지는 않지만 여기에서 완전성을 위해 언급합니다.
1/0
상수 표현에서 제외되지 않습니까?
1/0
은 ( 는) constant-expression 으로 구문 분석되지 않고 단순히 assignment-expression 의 일부인 조건식 으로 구문 분석되지 않기 때문에 질문의 맥락에서 상수 표현식이 아닙니다 . 제약 조건을 위반하고 진단이 필요합니다. case 1/0:
이 문서 에서는 섹션 2.6에서이 질문에 대해 설명합니다.
int main(void){
guard();
5 / 0;
}
저자는 프로그램이 guard()
종료되지 않을 때 정의 된 것으로 간주합니다 . 또한 "정적으로 정의되지 않은"및 "동적으로 정의되지 않은"개념을 구별합니다. 예 :
표준 11 의 의도 는 일반적으로 상황에 대한 코드를 생성하기가 쉽지 않은 경우 상황이 정적으로 정의되지 않은 상태로 만드는 것 같습니다. 코드를 생성 할 수있을 때만 상황이 동적으로 정의되지 않을 수 있습니다.
11) 위원과의 비공개 서신.
나는 전체 기사를 보는 것이 좋습니다. 함께 촬영하면 일관된 그림을 그립니다.
기사의 저자가위원회 위원과 질문에 대해 논의해야했다는 사실은 표준이 현재 귀하의 질문에 대한 답변에 모호하다는 것을 확인시켜줍니다.
guard()
의 동작이 정의되지 않은 것으로 가정 ) 명령문 5 / 0;
이 실제로 실행되는 경우에만 동작이 정의되지 않습니다 . (컴파일러는의 평가 5 / 0
를 abort()
또는 유사한 호출로 합법적으로 대체 할 수 있습니다 . 그러면 프로그램은 실행이 해당 지점에 도달하는 경우에만 중단됩니다.) 컴파일러 는 항상 종료 될 것이라고 결정할 수있는 경우에만 해당 프로그램을 거부 할 수 있습니다 guard()
.
guard()
종료되지 않는 것으로 결정하는 정교한 컴파일러는 5/0에 대해 코드를 전혀 생성 할 필요가 없습니다. 반대로에 대한 코드를 생성 (int)(void)5
할 수있는 방법이 없습니다 (int)(void)z
. 이것도 올바르지 않기 때문에에 대한 코드를 생성 할 수 없습니다 . 그래서 저자들은 이렇게 생각합니다…
if (0) (int)(void)5;
순진한 컴파일러에게 제시하는 수수께끼 때문에 프로그램을 거부 할 수 있지만, 이와 같은 도달 할 수없는 동적 UB if (0) 5 / 0;
는 무해합니다. 이것은위원회 위원과의 토론에서 일어난 일이며 다른 곳에서 비슷한 주장이 제기 된 것을 보았습니다 (그러나 아마도 같은 출처에서, 특히 그것이 어디에 있었는지 기억하지 못하기 때문에). 나는 현재 C99의 이론적 근거를 살펴보고 있는데, 이에 대한 언급이 있으면 다시 돌아와 지적하겠습니다.
(int)(void)5
제약 위반입니다. N1570 6.5.4, 캐스트 연산자 설명 : "제약 : 유형 이름이 무효 유형을 지정하지 않는 한 유형 이름은 원 자성, 규정 된 또는 규정되지 않은 스칼라 유형을 지정해야하며 피연산자는 스칼라 유형을 가져야합니다.". (void)5
스칼라 유형이 없으므로 (int)(void)5
이를 포함하는 코드가 실행되었는지 여부에 관계없이 해당 제약 조건을 위반합니다.
이 경우 정의되지 않은 동작은 코드를 실행 한 결과입니다. 따라서 코드가 실행되지 않으면 정의되지 않은 동작이 없습니다.
정의되지 않은 동작이 코드 선언의 결과 인 경우 실행되지 않은 코드는 정의되지 않은 동작을 호출 할 수 있습니다 (예 : 변수 섀도 잉의 일부 사례가 정의되지 않은 경우).
#include "//e"
UB를 호출하는 것을 고려하십시오 .
이 답변의 마지막 단락으로 가겠습니다. https://stackoverflow.com/a/18384176/694576
... UB는 컴파일 타임 문제가 아니라 런타임 문제입니다 ...
따라서 UB가 호출되지 않았습니다.
정의되지 않은 행동의 주제에서 공식적인 측면과 실제적인 측면을 분리하는 것은 종종 어렵습니다. 이것은 1989 표준에서 정의되지 않은 동작의 정의입니다 (최신 버전은 없지만 이것이 크게 변경 될 것으로 예상하지 않습니다).
정의되지 않은 동작 1 개 이식 불가능하거나 오류가있는 프로그램 구성을 사용하거나 이 국제 표준이 요구 사항을 부과하지 않는 잘못된 데이터 2 참고 가능한 정의되지 않은 동작은 상황을 완전히 무시하는 것에서부터 다양합니다. 예측할 수없는 결과, 번역 또는 프로그램 실행 중에 작동 문서화 된 방식으로 환경의 특성 (유무에 관계없이 진단 메시지 발행), 번역 종료 또는 실행 (진단 메시지 발행).
공식적인 관점에서 저는 여러분의 프로그램이 정의되지 않은 동작을 호출한다고 말하고 싶습니다. 즉, 표준이 0으로 나누기를 포함하기 때문에 실행될 때 수행 할 작업에 대한 요구 사항이 전혀 없음을 의미합니다.
반면에 실용적인 관점에서 직관적으로 예상 한대로 작동하지 않는 컴파일러를 발견하면 놀랄 것입니다.
표준은 내가 옳게 기억하는 것처럼 규칙이 깨지는 순간부터 무엇이든 할 수 있다고 말합니다. 어쩌면 일종의 세계적인 풍미를 가진 특별한 이벤트가있을 수 있습니다. false이므로 런타임시 규칙이 깨지지 않습니다.
여전히 정의되지 않은 행동이라고 생각하지만 표준에서 나를지지하거나 거부 할 증거를 찾을 수 없습니다.
프로그램이 정의되지 않은 동작을 호출하지 않는다고 생각합니다.
결함 보고서 # 109 는 유사한 질문을 다루며 다음과 같이 말합니다.
또한 주어진 프로그램의 모든 가능한 실행이 정의되지 않은 동작을 초래한다면 주어진 프로그램은 엄격하게 준수하지 않습니다. 준수 구현은 단순히 해당 프로그램의 일부 가능한 실행으로 인해 정의되지 않은 동작이 발생하기 때문에 엄격하게 준수하는 프로그램을 변환하는 데 실패해서는 안됩니다. foo는 절대 호출되지 않을 수 있기 때문에 주어진 예제는 준수 구현에 의해 성공적으로 번역되어야합니다.
"정의되지 않은 동작"이라는 표현이 정의 된 방법과 명령문의 "정의되지 않은 동작"이 프로그램의 "정의되지 않은 동작"과 동일한 지 여부에 따라 다릅니다.
이 프로그램은 C처럼 보이므로 컴파일러에서 사용하는 C 표준 (일부 답변과 마찬가지로)에 대한 심층 분석이 적절합니다.
지정된 표준이없는 경우 정답은 "에 따라 다릅니다"입니다. 일부 언어에서는 컴파일러가 추측 한 바에 따라 첫 번째 오류 이후 컴파일러가 프로그래머가 의미하는 바를 추측하고 여전히 일부 코드를 생성합니다. 다른 순수한 언어에서는 어떤 것이 정의되지 않으면 정의되지 않은 것이 전체 프로그램에 전파됩니다.
다른 언어에는 "제한된 오류"라는 개념이 있습니다. 일부 제한된 종류의 오류에 대해 이러한 언어는 오류로 인해 발생할 수있는 손상 정도를 정의합니다. 특히 묵시적 가비지 콜렉션이있는 특정 언어는 오류가 입력 시스템을 무효화하는지 여부에 관계없이 자주 차이를 만듭니다.