%p
변환 지정자로 널 포인터를 인쇄하는 것이 정의되지 않은 동작 입니까?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
질문은 C 구현이 아니라 C 표준에 적용됩니다.
답변
이것은 우리가 영어의 한계와 표준의 일관되지 않은 구조의 영향을받는 이상한 코너 사례 중 하나입니다. 그래서 기껏해야 증명할 수 없기 때문에 설득력있는 반론을 할 수 있습니다. 🙂 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. 표준 작성자가 나오거나 상황을 명확히 하는 근거 문서 와 유사한 것을 찾을 수없는 경우 .
답변
짧은 답변
네 . 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_t
n으로 선언 된 인수 가 함수의 배열 길이를 지정하는 경우 n은 해당 함수를 호출 할 때 값 0을 가질 수 있습니다. 이 하위 절의 특정 함수에 대한 설명에서 달리 명시 적으로 언급되지 않는 한, 그러한 호출에 대한 포인터 인수는 7.1.4에 설명 된 유효한 값을 가져야합니다.
의도적입니까?
%p
널 포인터가 있는 UB가 실제로 의도적 인 것인지 여부는 알 수 없지만 표준은 널 포인터가 표준 라이브러리 함수에 대한 인수로 유효하지 않은 값으로 간주된다는 것을 명시 적으로 명시한 다음 널이있는 경우를 명시 적으로 지정합니다. 포인터가 유효한 인수 (현재 snprintf, 무료 등)이며, 다음은 간다 다시 한번 인수도 제로에 유효하려면 ‘N’의 경우는 (요구 사항을 반복 memcpy
, memmove
, memset
), 나는 그것을 가정하는 합리적인 생각 C 표준위원회는 그러한 것들을 정의하지 않는 데 너무 관심이 없습니다.
답변
C 표준의 작성자는 구현이 특정 목적에 적합하기 위해 충족해야하는 모든 행동 요구 사항을 철저히 나열하려고 노력하지 않았습니다. 대신 그들은 컴파일러를 작성하는 사람들이 표준이 요구하는지 여부에 관계없이 어느 정도의 상식을 행사할 것이라고 기대했습니다.
무언가가 UB를 호출하는지 여부에 대한 질문은 그 자체로 거의 유용하지 않습니다. 중요한 질문은 다음과 같습니다.
-
고품질 컴파일러를 작성하려는 사람이 예측 가능한 방식으로 작동하도록해야합니까? 설명 된 시나리오의 경우 대답은 분명히 ‘예’입니다.
-
프로그래머는 일반 플랫폼과 유사한 모든 것에 대한 고품질 컴파일러가 예측 가능한 방식으로 작동 할 것이라고 기대할 자격이 있습니까? 설명 된 시나리오에서 대답은 ‘예’라고 말할 수 있습니다.
-
일부 둔한 컴파일러 작성자는 이상한 일을하는 것을 정당화하기 위해 표준의 해석을 확장 할 수 있습니까? 나는 원하지 않지만 그것을 배제하지는 않을 것입니다.
-
살균 컴파일러가 동작에 대해 삐걱 거리는가? 그것은 사용자의 편집증 수준에 달려 있습니다. 위생 컴파일러는 이러한 동작에 대해 삐걱 거리는 것을 기본으로해서는 안되지만, 프로그램이 이상하게 동작하는 “영리한”/ 멍청한 컴파일러로 이식 될 수있는 경우 수행 할 구성 옵션을 제공 할 수 있습니다.
표준에 대한 합리적인 해석이 동작이 정의되었음을 의미하지만 일부 컴파일러 작성자가 해석을 확장하여 그렇지 않은 경우를 정당화한다면 표준이 말하는 것이 실제로 중요합니까?