source

정의되지 않은 동작은 얼마나 정의되지 않습니까?

manycodes 2023. 10. 28. 08:05
반응형

정의되지 않은 동작은 얼마나 정의되지 않습니까?

저는 정의되지 않은 행동이 프로그램을 위험에 빠뜨릴 수 있는 정도를 잘 이해하지 못합니다.

내게 이런 코드가 있다고 치자.

#include <stdio.h>

int main()
{
    int v = 0;
    scanf("%d", &v);
    if (v != 0)
    {
        int *p;
        *p = v;  // Oops
    }
    return v;
}

이 프로그램의 동작은 다음과 같은 경우에만 정의되지 않습니까?v0이 아님 또는 다음과 같은 경우에도 정의되지 않음v0까입니까?

사용자가 0과 다른 숫자를 삽입하는 경우에만 동작이 정의되지 않습니다.결국, 불쾌 코드 섹션이 실제로 실행되지 않으면 UB에 대한 조건이 충족되지 않습니다(즉, 초기화되지 않은 포인터는 생성되지도 않고 참조되지도 않음).

이에 대한 힌트는 3.4.3의 표준에서 찾을 수 있습니다.

휴대가 불가능하거나 잘못된 데이터의 프로그램 구성자를 사용할 때, 본 국제 표준이 요구 사항을 부과하지 않는 거동

이것은 만약 그러한 "오류 데이터"가 정확했다면, 행동이 완벽하게 정의되었을 것이라는 것을 암시하는 것 같습니다. 이것은 우리의 경우에 거의 적용 가능한 것처럼 보입니다.


추가 예: 정수 오버플로입니다.사용자가 제공한 데이터에 대해 광범위한 검사를 수행하지 않고 추가를 수행하는 모든 프로그램은 이러한 종류의 정의되지 않은 동작의 대상이 됩니다. 그러나 추가는 사용자가 특정 데이터를 제공하는 경우에만 UB가 됩니다.

태그가 있기 때문에, 프로그램의 동작은 사용자 입력에 관계없이 정의되지 않지만, 사용자가 예상할 수 있는 이유로 정의되지 않는다는 극단적으로 비판하는 주장이 있습니다.v==0구현에 따라 달라질 수 있습니다.

프로그램이 정의합니다.main~하듯이

int main()
{
    /* ... */
}

C995.1.2.2.1 주 기능은 다음 중 하나로 정의되어야 한다고 합니다.

int main(void) { /* ... */ }

또는 로서

int main(int argc, char *argv[]) { /* ... */ }

또는 이와 동등한 방법, 또는 다른 구현 정의 방식.

int main()에 해당하지 않습니다.int main(void). 전자는, 선언으로서, 다음과 같이 말합니다.main는 고정되어 있지만 지정되지 않은 수의 인수와 유형을 사용합니다. 후자는 인수를 사용하지 않는다고 말합니다.다른 점은 재귀적인 호출이main예를 들어

main(42);

를 사용하는 경우 제약 조건 위반입니다.int main(void), 사용하면 안 됩니다.int main().

예를 들어, 이 두 프로그램은 다음과 같습니다.

int main() {
    if (0) main(42); /* not a constraint violation */
}


int main(void) {
    if (0) main(42); /* constraint violation, requires a diagnostic */
}

동치가 아닙니다.

그것이 수락하는 구현 문서가 있는 경우int main()확장으로, 이는 해당 구현에 적용되지 않습니다.

이것은 (모두가 동의하지 않는) 극도의 트집 잡기 포인트이며, 선언함으로써 쉽게 피할 수 있습니다.int main(void)(어쨌든 이 작업을 수행해야 합니다. 모든 함수에는 구형 선언/정의가 아닌 프로토타입이 있어야 합니다.)

실제로 내가 본 모든 컴파일러들은int main()불평없이

의도한 질문에 답하기

그 변화가 이루어지면, 프로그램의 행동은 다음과 같이 잘 정의됩니다.v==0, 다음과 같은 경우에 정의되지 않습니다.v!=0 예, 프로그램 동작의 정의는 사용자 입력에 따라 달라집니다.특별히 특이한 점은 없습니다.

제가 왜 이것이 아직도 정의되지 않았다고 생각하는지에 대한 논거를 제시하겠습니다.

첫째, 일부 컴파일러에 대한 경험을 바탕으로 이것이 "대부분 정의된" 것이라고 말하는 응답자들은 틀렸습니다.예제를 약간 수정하면 다음을 설명하는 데 도움이 됩니다.

#include <stdio.h>

int
main()
{
    int v;
    scanf("%d", &v);
    if (v != 0)
    {
        printf("Hello\n");
        int *p;
        *p = v;  // Oops
    }
    return v;
}

"1"을 입력으로 제공하면 이 프로그램은 어떤 역할을 합니까?"Hello를 인쇄한 다음 충돌합니다"라고 대답하면 틀립니다."정의되지 않은 동작"은 특정 문장의 동작이 정의되지 않음을 의미하는 것이 아니라, 전체 프로그램의 동작이 정의되지 않음을 의미합니다.컴파일러는 사용자가 정의되지 않은 행동을 하지 않는다고 가정할 수 있으므로, 이 경우 다음과 같이 가정할 수 있습니다.v0이 아니며 단순히 다음을 포함하여 괄호로 표시된 코드를 전혀 방출하지 않습니다.printf.

만약 이것이 가능성이 없다고 생각한다면, 다시 생각해보세요.GCC는 이 분석을 정확하게 수행하지는 않지만 매우 유사한 분석을 수행합니다.실제로 포인트를 실제로 보여주는 제가 가장 좋아하는 예는:

int test(int x) { return x+1 > x; }

출력할 작은 테스트 프로그램을 작성해 봅니다.INT_MAX,INT_MAX+1,그리고.test(INT_MAX) (최적화를 활성화해야 합니다.)일반적인 구현을 통해INT_MAX 2147483647,INT_MAX+1 -2147483648,-test(INT_MAX)1이 될 것입니다.

사실, GCC는 상수 1을 반환하기 위해 이 함수를 컴파일합니다. 왜일까요?정수 오버플로는 정의되지 않은 동작이기 때문에 컴파일러는 당신이 그렇게 하고 있지 않다고 가정할 수 있고, 따라서 x는 같을 수 없습니다.INT_MAX,그러므로x+1 .x, 따라서 이 함수는 무조건 1을 반환할 수 있습니다.

정의되지 않은 행동은 자신과 동일하지 않은 변수, 양수보다 큰 음수(위의 예 참조) 및 기타 기이한 행동을 초래할 수 있습니다.컴파일러가 영리할수록 기이한 행동을 합니다.

알겠습니다, 질문하신 정확한 질문에 답하기 위해 표준의 장과 절을 인용할 수 없음을 인정합니다.하지만 "그래, 하지만 실제로는 NULL이 seg fault를 줄 뿐이야"라고 말하는 사람들은 상상할 수 있는 것보다 더 잘못되었고, 모든 컴파일러 세대에서 더 잘못되고 있습니다.

그리고 실제로는 코드가 비활성화되어 있으면 코드를 제거해야 하고, 비활성화되어 있지 않으면 정의되지 않은 동작을 호출해서는 안 됩니다.그게 당신의 질문에 대한 제 대답입니다.

v가 0이면 랜덤 포인터 할당이 실행되지 않으며 함수가 0을 반환하므로 정의되지 않은 동작이 아닙니다.

변수(특히 명시적 포인터)를 선언하면 메모리 조각이 할당됩니다(보통 int).이 기억의 평화는 다음과 같이 표시되고 있습니다.free시스템에 저장된 이전 값이 지워지지 않았습니다. (이것은 컴파일러가 구현하는 메모리 할당에 따라 달라지며, 그 자리를 0으로 채울 수도 있습니다.) 그래서 당신은int *p로 해석해야 하는 임의의 값(junk)을 가질 것입니다.integer. 그 결과는 기억 속의 장소입니다.p(p's pointee) 를 가리킵니다.당신이 하려고 할 때dereference(일명 이 메모리 부분에 액세스), 다른 프로세스/프로그램이 거의 매번 사용하므로 다른 메모리를 변경/modify하려고 하면 결과적으로access violation에 의한 쟁점들memory manager.

따라서 이 예에서, 0과 같은 다른 값은 정의되지 않은 동작을 초래할 것입니다. 왜냐하면 아무도 무엇을 알지 못하기 때문입니다.*p이 순간을 가리킬 것입니다.

이 설명이 도움이 되기를 바랍니다.

편집 : 아 죄송합니다, 다시 몇가지 답변이 앞에 있습니다 :)

그것은 간단하다.코드 조각이 실행되지 않으면 동작이 없습니다!!! 정의되었든 아니든 간에.

입력이 0이면 내부의 코드가if실행되지 않으므로 동작이 정의되었는지 여부는 프로그램의 나머지 부분에 따라 결정됩니다(이 경우 정의됨).

입력이 0이 아닌 경우, 우리 모두가 정의되지 않은 동작의 경우라는 것을 알고 있는 코드를 실행합니다.

그것이 전체 프로그램을 정의하지 못하게 만든다고 말할 수 있습니다.

정의되지 않은 행동의 핵심은 정의되지 않았다는 것입니다.컴파일러는 그 문장을 보고 싶은 대로 할 수 있습니다.이제 모든 컴파일러가 예상대로 처리하겠지만, 이와 무관한 부분을 변경하는 것을 포함하여 원하는 것은 무엇이든 할 수 있는 모든 권리가 있습니다.

예를 들어, 컴파일러가 정의되지 않은 동작을 감지할 경우 "이 프로그램은 위험할 수 있습니다"라는 메시지를 프로그램에 추가하도록 선택할 수 있습니다.이렇게 하면 출력이 변경됩니다.v0 입니다.

당신의 프로그램은 아주 잘 정의되어 있습니다.v == 0이면 0을 반환합니다.v!=0이면 메모리의 임의의 지점에 튄다.

p는 포인터이며 초기값은 무엇이든 될 수 있습니다. 초기화를 하지 않기 때문입니다.실제 값은 운영 체제(프로세스에 제공하기 전에 메모리가 0인 경우도 있고 그렇지 않은 경우도 있습니다), 컴파일러, 하드웨어 및 프로그램을 실행하기 전에 메모리에 무엇이 있는지에 따라 달라집니다.

포인터 할당은 임의의 메모리 위치에 쓰는 것입니다.성공할 수도 있고, 다른 데이터를 손상시킬 수도 있고, 고장일 수도 있습니다. 위의 모든 요소에 따라 달라집니다.

C에 관한 한, 초기화되지 않은 변수들은 알려진 값을 갖지 않는다는 것은 꽤 잘 정의되어 있으며, 당신의 프로그램은 (컴파일될 수는 있지만) 정확하지 않을 것입니다.

언급URL : https://stackoverflow.com/questions/7961067/how-undefined-is-undefined-behavior

반응형