% p로 널 포인터를 인쇄하는 것은 정의되지 않은 동작입니까?


93

%p변환 지정자로 널 포인터를 인쇄하는 것이 정의되지 않은 동작 입니까?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

질문은 C 구현이 아니라 C 표준에 적용됩니다.


A는 실제로 누구 (C위원회 포함)도 그것에 대해 너무 많은 관심을 갖고 있다고 생각하지 않습니다. 실질적인 의미가 없거나 거의없는 아주 인위적인 문제입니다.
P__J__

printf는 값만 표시하고 터치하지 않습니다 (뾰족한 객체를 읽거나 쓰는 의미에서 )
-UB

3
@PeterJ 당신이 말하는 것이 사실이라고 말합시다 (표준은 분명히 그렇지는 않지만), 우리가 이것에 대해 토론하고 있다는 사실만으로도 아래 인용 된 표준 부분과 같이 보이기 때문에 질문이 유효하고 올바른 질문이됩니다. 도대체 무슨 일이 일어나고 있는지 일반 개발자에게는 이해하기가 매우 어렵습니다. 의미 :이 문제는 설명이 필요하기 때문에이 질문은 반대표를받을 가치가 없습니다!
Peter Varo


2
다른 이야기는 다음의 @PeterJ, :) 정화 주셔서 감사합니다
피터 VARO

답변:


93

이것은 우리가 영어의 한계와 표준의 일관되지 않은 구조의 영향을받는 이상한 코너 사례 중 하나입니다. 그래서 기껏해야 증명할 수 없기 때문에 설득력있는 반론을 할 있습니다. :) 1


질문의 코드는 잘 정의 된 동작을 보여줍니다.

[7.1.4] 질문의 기초이며,의가 시작하자 :

다음의 각 설명은 다음 세부 설명에서 명시 적으로 달리 명시되지 않는 한 적용됩니다. 함수에 대한 인수에 유효하지 않은 값 ( 예 : 함수 도메인 외부의 값 또는 프로그램의 주소 공간 외부에있는 포인터, 또는 널 포인터 , [... 기타 예 ...] ) [...] 동작이 정의되지 않았습니다.[... 기타 진술 ...]

서투른 언어입니다. 한 가지 해석은 개별 설명에 의해 재정의되지 않는 한 목록의 항목이 모든 라이브러리 기능에 대해 UB라는 것입니다. 그러나 목록은 "예를 들어"로 시작하여 완전한 것이 아니라 예시임을 나타냅니다. 예를 들어, 문자열의 올바른 null 종료에 대해 언급하지 않습니다 (예 :의 동작에 중요 strcpy).

따라서 7.1.4의 의도 / 범위는 단순히 "잘못된 값"이 UB로 연결된다는 것입니다 ( 달리 명시되지 않는 한 ). "잘못된 값"으로 간주되는 항목을 확인하려면 각 함수의 설명을 확인해야합니다.

예 1- strcpy

[7.21.2.3] 은 다음과 같이 말합니다.

strcpy함수는가 가리키는 문자열 s2(종료 널 문자 포함)을가 가리키는 배열에 복사합니다 s1. 겹치는 객체간에 복사가 발생하면 동작이 정의되지 않습니다.

널 포인터를 명시 적으로 언급하지 않지만 널 종료 자에 대해서도 언급하지 않습니다. 대신에 "가 가리키는 문자열에서 추론합니다.s2 "에서 유일하게 유효한 값이 문자열 (즉, 널로 끝나는 문자 배열에 대한 포인터)임을 합니다.

실제로이 패턴은 개별 설명 전체에서 볼 수 있습니다. 몇 가지 다른 예 :

  • [7.6.4.1는 (fenv)] 에서 현재 부동 소수점 저장 환경 에 뾰족한 물체 에 의해envp

  • [7.12.6.4 (frexp와는)] INT의 정수의 저장 을 뾰족한 작성자exp

  • [7.19.5.1 (FCLOSE)]을 스트림을 지적 으로stream

예 2- printf

[7.19.6.1] 은 다음에 대해 이렇게 말합니다 %p.

p-인수는에 대한 포인터 여야합니다 void. 포인터의 값은 구현 정의 방식으로 일련의 인쇄 문자로 변환됩니다.

Null은 유효한 포인터 값이며이 섹션에서는 null이 특별한 경우이거나 포인터가 개체를 가리켜 야한다는 명시적인 언급을하지 않습니다. 따라서 그것은 정의 된 행동입니다.


1. 표준 작성자가 나오거나 상황을 명확히 하는 근거 문서 와 유사한 것을 찾을 수없는 경우 .


의견은 확장 된 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Bhargav Rao

1
"아직 null 종결자를 언급하지 않습니다"는 예 1에서 약합니다-strcpy는 사양이 " 문자열을 복사합니다"라고 말합니다 . stringnull 문자 를 갖는 것으로 명시 적으로 정의됩니다 .
chux - 분석 재개 모니카

1
@chux-그건 내 요점 입니다. 7.1.4의 목록이 완전하다고 가정하기보다는 컨텍스트에서 무엇이 유효 / 무효인지 추론 해야합니다. (단, 내 대답이 부분의 존재는 strcpy와는 반증이라고 주장, 삭제 된 이후이 의견의 맥락에서 좀 더 이해했다.)
올리버 찰스 워드

1
문제의 핵심은 독자가 해석하는 방법이다 . 는 뜻 의 몇 가지 예 가능합니다 잘못된 값됩니다 ? 항상 유효하지 않은 값이 몇 가지 예를 의미합니까 ? 기록을 위해 첫 번째 해석을 사용합니다.
ninjalj

1
@ninjalj-네, 동의합니다. 그것은 본질적으로 여기 내 대답에서 전달하려는 것입니다. :)
올리버 찰스 워드

20

짧은 답변

. Null 포인터 인쇄%p변환 지정자를 동작이 정의되지 않습니다. 그렇긴하지만 오작동하는 기존 준수 구현을 알지 못합니다.

대답은 모든 C 표준 (C89 / C99 / C11)에 적용됩니다.


긴 답변

그만큼 %p무효로 유형 포인터의 인수를 기대 지정 변환, 인쇄 가능한 문자에 대한 포인터의 변환은 구현 정의이다. 널 포인터가 예상된다는 것을 나타내지 않습니다.

표준 라이브러리 함수에 대한 소개에서는 (표준 라이브러리) 함수에 대한 인수로서의 널 포인터가 명시 적으로 달리 명시되지 않는 한 유효하지 않은 값으로 간주됨을 명시합니다.

C99 / C11 §7.1.4 p1

[...] 함수에 대한 인수에 잘못된 값 (예 : [...] 널 포인터, [...])이있는 경우 동작이 정의되지 않습니다.

유효한 인수로 널 포인터를 예상하는 (표준 라이브러리) 함수의 예 :

  • fflush() "모든 스트림"(적용되는)을 플러시하기 위해 널 포인터를 사용합니다.
  • freopen() 스트림과 "현재 연관된"파일을 나타내는 데 널 포인터를 사용합니다.
  • snprintf() 'n'이 0 일 때 널 포인터를 전달할 수 있습니다.
  • realloc() 새 개체를 할당하기 위해 널 포인터를 사용합니다.
  • free() 널 포인터를 전달할 수 있습니다.
  • strtok() 후속 호출에 널 포인터를 사용합니다.

에 대한 경우를 취 snprintf()하면 'n'이 0 일 때 널 포인터 전달을 허용하는 것이 합리적이지만 유사한 0 'n'을 허용하는 다른 (표준 라이브러리) 함수의 경우는 그렇지 않습니다. 예를 들면 : memcpy(), memmove(), strncpy(), memset(), memcmp().

표준 라이브러리 소개뿐만 아니라 다음 함수 소개에서도 다시 한 번 지정됩니다.

C99 §7.21.1 p2 / C11 §7.24.1 p2

size_tn으로 선언 된 인수 가 함수의 배열 길이를 지정하는 경우 n은 해당 함수를 호출 할 때 값 0을 가질 수 있습니다. 이 하위 절의 특정 함수에 대한 설명에서 달리 명시 적으로 언급되지 않는 한, 그러한 호출에 대한 포인터 인수는 7.1.4에 설명 된 유효한 값을 가져야합니다.


의도적입니까?

%p널 포인터가 있는 UB가 실제로 의도적 인 것인지 여부는 알 수 없지만 표준은 널 포인터가 표준 라이브러리 함수에 대한 인수로 유효하지 않은 값으로 간주된다는 것을 명시 적으로 명시한 다음 널이있는 경우를 명시 적으로 지정합니다. 포인터가 유효한 인수 (현재 snprintf, 무료 등)이며, 다음은 간다 다시 한번 인수도 제로에 유효하려면 'N'의 경우는 (요구 사항을 반복 memcpy, memmove, memset), 나는 그것을 가정하는 합리적인 생각 C 표준위원회는 그러한 것들을 정의하지 않는 데 너무 관심이 없습니다.


의견은 확장 된 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Bhargav Rao

1
@JeroenMostert :이 주장의 의도는 무엇입니까? 7.1.4의 주어진 인용문은 다소 명확하지 않습니까? 에 대해 논쟁 무엇있다 "명시 적으로 달리 명시되지 않는" 이 때 되지 않는 별도의 언급이? (관련되지 않은) 문자열 함수 라이브러리에 유사한 문구가 있으므로 문구가 우연한 것 같지 않다는 사실에 대해 무엇을 논할 수 있습니까? 나는이 대답 (동안 정말 유용하지 생각 실제로이 )가 될 수있는만큼 정확합니다.
Damon

3
@Damon : 귀하의 신화적인 하드웨어는 신화적인 것이 아닙니다. 유효한 주소를 나타내지 않는 값이 주소 레지스터에로드되지 않을 수있는 아키텍처가 많이 있습니다. 그러나 이러한 플랫폼에서 일반적인 메커니즘으로 작동하려면 함수 인수로 널 포인터를 전달해야합니다. 단지 하나를 스택에 올려 놓는 것은 일을 날려 버리지 않습니다.
Jeroen Mostert 2017

1
@anatolyg : x86 프로세서에서 주소에는 세그먼트와 오프셋이라는 두 부분이 있습니다. 8086에서 세그먼트 레지스터를로드하는 것은 다른 항목을로드하는 것과 비슷하지만 이후의 모든 시스템에서 세그먼트 설명자를 가져옵니다. 잘못된 설명자를로드하면 트랩이 발생합니다. 그러나 80386 이상의 프로세서에 대한 많은 코드는 하나의 세그먼트 만 사용하므로 세그먼트 레지스터 전혀로드하지 않습니다 .
supercat

1
나는 모두가 널 포인터를 인쇄하는 %p것이 정의되지 않은 동작이 아니라는 데 동의 할 것이라고 생각합니다
MM

-1

C 표준의 작성자는 구현이 특정 목적에 적합하기 위해 충족해야하는 모든 행동 요구 사항을 철저히 나열하려고 노력하지 않았습니다. 대신 그들은 컴파일러를 작성하는 사람들이 표준이 요구하는지 여부에 관계없이 어느 정도의 상식을 행사할 것이라고 기대했습니다.

무언가가 UB를 호출하는지 여부에 대한 질문은 그 자체로 거의 유용하지 않습니다. 중요한 질문은 다음과 같습니다.

  1. 고품질 컴파일러를 작성하려는 사람이 예측 가능한 방식으로 작동하도록해야합니까? 설명 된 시나리오의 경우 대답은 분명히 '예'입니다.

  2. 프로그래머는 일반 플랫폼과 유사한 모든 것에 대한 고품질 컴파일러가 예측 가능한 방식으로 작동 할 것이라고 기대할 자격이 있습니까? 설명 된 시나리오에서 대답은 '예'라고 말할 수 있습니다.

  3. 일부 둔한 컴파일러 작성자는 이상한 일을하는 것을 정당화하기 위해 표준의 해석을 확장 할 수 있습니까? 나는 원하지 않지만 그것을 배제하지는 않을 것입니다.

  4. 살균 컴파일러가 동작에 대해 삐걱 거리는가? 그것은 사용자의 편집증 수준에 달려 있습니다. 위생 컴파일러는 이러한 동작에 대해 삐걱 거리는 것을 기본으로해서는 안되지만, 프로그램이 이상하게 동작하는 "영리한"/ 멍청한 컴파일러로 이식 될 수있는 경우 수행 할 구성 옵션을 제공 할 수 있습니다.

표준에 대한 합리적인 해석이 동작이 정의되었음을 의미하지만 일부 컴파일러 작성자가 해석을 확장하여 그렇지 않은 경우를 정당화한다면 표준이 말하는 것이 실제로 중요합니까?


1. 프로그래머가 현대적 / 공격적 최적화 프로그램의 가정이 "합리적"또는 "품질"이라고 생각하는 것과 상충하는 것을 찾는 것은 드문 일이 아닙니다. 2. 사양의 모호성에 관해서는 구현자가 자신이 가정 할 수있는 자유에 대해 상충하는 경우가 드물지 않습니다. 는 C 표준위원회의 회원들에게 제공하는 경우 3. 심지어 그들은 항상 '올바른'해석은, 그것은 무엇을 말할 것도없고 무엇에 관해서는 동의하지 않는다 해야 합니다. 앞서 말했듯이 우리는 누구의 합리적인 해석을 따라야합니까?
Dror K.

6
UB의 유용성 또는 컴파일러가 어떻게 작동해야하는지에 대한 논문과 함께 "이 특정 코드가 UB를 호출합니까?"라는 질문에 대답하는 것은 특히 다음과 같이 복사하여 붙여 넣을 수 있기 때문에 대답을 제대로 시도하지 못합니다. 특정 UB에 대한 거의 모든 질문에 대한 답변입니다. 당신의 수사학의 번성에 대한 재조명으로서 : 그렇습니다. 일부 컴파일러 작성자가 무엇을하든, 당신이 그것을 수행하는 것에 대해 당신이 생각하는 것과 상관없이 표준이 말하는 것이 정말 중요합니다. 왜냐하면 표준은 프로그래머와 컴파일러 작성자 모두가 시작하는 것이기 때문입니다.
Jeroen Mostert 2017

1
@JeroenMostert : "X가 정의되지 않은 동작을 호출합니까"에 대한 대답은 종종 질문이 의미하는 바에 따라 달라집니다. 표준이 준수하는 구현의 동작에 대한 요구 사항을 부과하지 않는 경우 프로그램이 정의되지 않은 동작을 갖는 것으로 간주되면 거의 모든 프로그램이 UB를 호출합니다. 표준의 작성자는 구현이 Stadard에서 번역 제한을 실행하는 하나 이상의 (아마도 인위적인) 소스 텍스트를 올바르게 처리 할 수있는 한, 프로그램이 함수 호출을 너무 깊게 중첩하는 경우 임의의 방식으로 구현되도록 허용합니다.
supercat

@supercat : 매우 흥미롭지 만 printf("%p", (void*) 0)표준에 따르면 정의되지 않은 동작입니까? 깊이 중첩 된 함수 호출은 중국의 차 가격만큼 관련이 있습니다. 그리고 예, UB는 실제 프로그램에서 매우 일반적입니다.
Jeroen Mostert 2017

1
@JeroenMostert : 표준은 둔각 구현이 거의 모든 프로그램이 UB를 갖는 것으로 간주하도록 허용 할 것이기 때문에, 중요한 것은 둔각이 아닌 구현의 동작 일 것입니다. 눈치 채지 못했다면 UB에 대한 복사 / 붙여 넣기 만하는 것이 아니라 질문의 %p가능한 각 의미에 대한 질문에 답했습니다.
supercat jul
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.