C++ NULL에 대해서 질문합니다.

hojak99의 이미지

 
void aa(int){
  std::cout<<"Call aa "<<std::endl
}
void aa(int *) {
  std::cout<<"Call *aa"<<std::endl;
}
 
int main()
{
   aa(NULL);
   return 0;
}

위의 코드를 출력시켰을 때 "Call aa"를 출력하게 됩니다. "Call aa"를 출력하는 이유가 NULL이 0을 뜻하고 있어서 인가요?

뭔가 확실하게 아는 것 같지 않아서 질문 합니다.

 의 이미지

NULL은 C++ 타입시스템의 노출된 특이점 같은 존재입니다.

이 녀석은 포인터가 들어갈 수 있는 자리라면 그게 무슨 타입을 가리키는 포인터든 들어갈 수 있어야 한다는 사명을 부여받았는데, 생각해 보면 그게 가능한 C++ 타입은 없어요.
네, void *를 생각하시는 분이 계시겠죠. 하지만 어떤 포인터든 void *가 될 수는 있어도, 그 역, 그러니까 void *가 다른 포인터로 함부로 변환될 수는 없는 거죠.

덕분에 C++에서 NULL의 타입은 붕 뜨게 되는데, 정확히 어떤 타입일지는 구현환경마다 다릅니다. 심지어 NULL이 그냥 정수 0으로 정의되어 있을 수도 있어요. 일견 황당하지만, 실제로 컴파일러가 특정 포인터 타입을 더 선호할 이유는 없다는 점에서, 그리고 많은 C/C++ 프로그래머들이 NULL 대신 그냥 0을 쓰기도 한다는 점에서 그럴듯한 판단이죠. 근데 그러면 질문자님이 보시는 것과 같은 사태가 일어나는 겁니다.

평소라면 "에이, 까짓것 그냥 강제로 캐스팅해버리거나 그냥 경고 띄우게 냅두고 무시하자" 해 버리면 되지만, 질문자님의 경우처럼 오버로딩 버전 선택을 위해 사용하거나 템플릿 인자 추론을 맡긴다던가 할 때가 되면 무시 못 할 문제가 생기게 됩니다. 이럴 땐 명시적으로 NULL을 캐스팅해서 넘기는 수밖에요. static_cast<int *>(NULL) 하면 되겠죠...?

물론 이 꼴을 보다 못한 C++의 높으신 분들께서 친히 어여삐 여기시어 C++11부터는 nullptr라는 상수가 추가됐습니다. 이 놈은 std::nullptr_t라는 희한한 타입을 가지는데, 실상 앞서 언급한 타입시스템의 특이점을 살짝 가려 주는 역할을 합니다. 이 상수를 쓰면 별도로 캐스팅하지 않아도 항상 포인터를 받는 버전이 선택될 겁니다. (물론 포인터를 받는 오버로딩 버전이 둘 이상이면 여전히 문제죠. 근데 그건 C++ 탓이 아닙니다. 그렇게 짠 사람 탓이지.)

hojak99의 이미지

감사합니다! 무슨 말씀하시는지 알겠습니다

twinwings의 이미지

너무 여럽게 설명한 것 같습니다.
NULL은 단지

#define NULL ((void*)0)

으로 정의된 메크로에 불과합니다. 이게 정의이고, 정의가 이렇기 때문에

"타입이 없다." 라는 특징이 나오는 것이구요.

DarkSide의 이미지

C++과 C는 다릅니다.
너무 쉽게 생각하시는 듯.

twinwings의 이미지

C++의 표준헤더에 있는 NULL도 macro로 정의됩니다.

http://www.cplusplus.com/reference/cstddef/

gcc의 cstddef.h 문서: https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-3.4/cstddef-source.html

clang의 stddef.h 문서: http://clang.llvm.org/doxygen/stddef_8h_source.html

문서 참조하십시오.

 의 이미지

태클을 걸어주신 덕분에 더 찾아보게 되는군요. 그런 재미로 답변 다는 거죠.

제시하신 코드는 NULL의 정의가 아닙니다.
NULL의 정의는 C11, C++03, C++11 셋 모두에서 아래와 같이 제공합니다.

C11, C++03 and C++11 wrote:
A macro which expands to an implementation-defined null pointer constant

약간의 표현 차이는 있지만 중요하지 않아서 그냥 정리했습니다. 요점은 implementation-definednull pointer constant거든요.

여기서 null pointer constant의 의미는 표준에 따라 아주 미묘하게 다릅니다.

C11 wrote:
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

C++03 wrote:
A null pointer constant is an integral constant expression rvalue of integer type that evaluates to zero. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function type. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion.

C++11 wrote:
A null pointer constant is an integral constant expression prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion. A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t.

C언어에서는 ((void *)0)NULL의 정의일 수 있지만 C++03과 C++11에서는 그렇지 않군요. C언어에서는 정수 0뿐만 아니라 0을 (void *)로 캐스팅한 값도 null pointer constant인 반면, C++03에서는 0으로 평가되는, 정수 타입의 정수 상수 표현식이 null pointer constant라고 규정하고 있기 때문입니다. C++11에서는 여기에 std::nullptr_t 관련 내용이 추가됐을 뿐이고요.

이렇게 되면 사실 제 원래의 답변에도 흠집이 생기게 됩니다. C++03 기준으로는 NULL이 정수 0으로 정의되어 있을 수 있다가 아니라, 그냥 확실히 정수 상수 0이다라고 해야겠군요. 구체적인 타입은 int이든 unsigned int이든 달라질 수도 있겠지만요.

역시 답변을 달려면 제대로 알아보고 해야 하는 모양입니다. 쩝.

이제 왜 질문자님의 코드에서 NULL이 정수를 받는 오버로딩 버전을 선택하는지도 분명해집니다. NULL이 애초에 그냥 정수 타입의 정수였으니까요. 물론 NULLint인지 아닌지는 알 수 없습니다만, 오버로드된 함수를 호출할 때 어떤 함수가 선택되는지에 대한 미친듯이 복잡한 표준 규칙을 찾아보면 아무래도 상관 없이 int를 받는 버전이 선택된다는 점을 알 수 있습니다. Standard conversions, 그 중에서도 Integral promotions 또는 Integral conversions이 적용되거든요.

오컴의 면도날 같은 있어보이는 이름은 차치하고서라도, 쉬운 설명과 어려운 설명이 있다면 저도 물론 쉬운 설명을 선호합니다. 하지만 그 이전에 1)정확해야 하고 2)질문자님의 질문을 커버할 수 있어야 하지요.

저는 최선을 다했습니다. twinwings님의 답변은 어떠한가요? 개인적으로 저는 ((void*)0)라고 직접 명시적으로 캐스팅하셨으면서도 타입이 없다고 말씀하신 부분에 대한 이론적 근거가 무척 궁금합니다. 또한 그 내용이 C++에서의 오버로딩 버전 선택을 묻는 질문을 어떻게 커버하고 있는지에 대해서도 의문이 있고요.

hojak99의 이미지

전에 답글 달아주신 것보고 무슨 말씀을 하시는 것인지 알 것 같아서 구글에 좀 더 검색을 해보고 예제도 보았는데 이번 답글보고 감사할따름입니다 ㅠㅠㅠ

twinwings의 이미지

제가 말한 정의는 음.. 단어의 정의가 아니라

prototype과 implementation의 관점에서의 정의를 뜻합니다.

컴파일(링크) 할 때 "정의를 찾을 수 없습니다."와 같은 표현으로요.

오해할 수 있겠군요.

 의 이미지

아니요. 저는 오해하지 않았습니다.
다만 표준을 따르는 implementation에서의 NULL 정의는 당연히 표준의 정의를 따라갑니다.
당연하지요. 그러라고 있는 표준이니까요.

꼭 표준에서의 정의가 아닌 실제 implementation에서의 정의를 보고 싶으시다면, 멀리 갈 것도 없습니다.
직접 링크하신 clang의 stddef.h 문서도 제가 말씀드린 내용의 좋은 예가 되는군요.

#if defined(__need_NULL)
#undef NULL
#ifdef __cplusplus
#  if !defined(__MINGW32__) && !defined(_MSC_VER)
#    define NULL __null
#  else
#    define NULL 0
#  endif
#else
#  define NULL ((void*)0)
#endif

C언어에서는 NULL이 정수 상수 0일 수도 있고 (void *)로 캐스팅된 정수 상수 0일 수도 있지만(후자를 택했군요) C++의 경우는 반드시 정수 상수 0이어야 한다는 표준 규정을 훌륭하게 준수하는 모습입니다.

링크하시진 않았지만, gcc 4.8.4의 stddef.h도 대략 비슷합니다.

#if defined (_STDDEF_H) || defined (__need_NULL)
#undef NULL     /* in case <stdio.h> has defined it. */
#ifdef __GNUG__
#define NULL __null
#else   /* G++ */
#ifndef __cplusplus
#define NULL ((void *)0)
#else   /* C++ */
#define NULL 0
#endif  /* C++ */
#endif  /* G++ */
#endif  /* NULL not defined and <stddef.h> or need NULL.  */
#undef  __need_NULL

이 질문은 명백히(제목은 차치하고서라도, C언어에 함수 오버로딩이 있던가요?) C++ 질문이므로 C++의 규칙을 따라야죠.

그건 그렇고, 표준에도 분명히 명시되어 있듯 NULL은 확실히 매크로가 맞습니다만, 그 사실이 이 질문에 있어서 무슨 의미를 가지지요?

익명 사용자의 이미지

C는 void* 형과 다른 포인터 형이 호환되기 때문에 ((void*)0)과 같이 정의될 수 있습니다.
하지만 C++에선 그렇지 않기 때문에 보통 0을 씁니다. implementation에서 정의된 다른 내부적인 구문을 쓸 수도 있겠지만, 적어도 ((void *)0)은 쓸 수 없습니다.

C++11에서는 거기에 nullptr_t같은게 추가된 모양이군요

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.