Что такое null в си
Перейти к содержимому

Что такое null в си

  • автор:

NULL Pointer in C

The Null Pointer is the pointer that does not point to any location but NULL. According to C11 standard:

“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.”

Syntax of Null Pointer Declaration in C

We just have to assign the NULL value. Strictly speaking, NULL expands to an implementation-defined null pointer constant which is defined in many header files such as “stdio.h”, “stddef.h”, “stdlib.h” etc.

Uses of NULL Pointer in C

Following are some most common uses of the NULL pointer in C:

  1. To initialize a pointer variable when that pointer variable hasn’t been assigned any valid memory address yet.
  2. To check for a null pointer before accessing any pointer variable. By doing so, we can perform error handling in pointer-related code, e.g., dereference a pointer variable only if it’s not NULL.
  3. To pass a null pointer to a function argument when we don’t want to pass any valid memory address.
  4. A NULL pointer is used in data structures like trees, linked lists, etc. to indicate the end.

Check if the pointer is NULL

It is a valid operation in pointer arithmetic to check whether the pointer is NULL. We just have to use isequal to operator ( == ) as shown below:

The above equation will be true if the pointer is NULL, otherwise, it will be false.

A Guide to NULL in C

Most programming languages have some concept of null values. Generally, null is a value that represents nothing and therefore is usually used to represent the absence of a value when a variable is not initialized.

For example, JavaScript uses null , Python uses None , and Ruby uses nil .

While null is usually used to represent the absence of a value, in C, it is used to represent a null pointer.

When you want to initialize a pointer but don’t yet have a value, you can use NULL .

To ensure that you don’t get the use of undeclared identifier error, make sure to include the stdio.h header file that comes with C.

In addition to using it as a value to set a new pointer to, you can also use NULL to check if variables are pointing to a valid address or not.

Here’s how to check if a pointer is a null pointer or not in C:

Under the hood, NULL is just a constant pointer guaranteed to not point to any valid address.

In some cases, you could replace NULL with 0 to get the same result, but the intent of your code will be more clear if you use NULL instead.

Conclusion

Hopefully, this post gave you a quick overview of how NULL works in C.

You can use NULL to initialize a variable to point to nothing, or use it to check if a pointer is a null pointer or not.

If you want to learn about web development, founding a start-up, bootstrapping a SaaS, and more, follow me on Twitter! You can also join the conversation over at our official Discord!

NULL (Си)

NULL в языках программирования Си и C++ — макрос, объявленный в заголовочном файле stddef.h (и других заголовочных файлах). Значением этого макроса является зависящая от реализации константа нулевого указателя (англ.  null pointer constant ). Константа нулевого указателя — это целочисленное константное выражение со значением 0, или (только в Си) такое же выражение, но приведённое к типу void * . Константа нулевого указателя, приведённая к любому типу указателей, является нулевым указателем. Гарантируется, что нулевой указатель не равен указателю на любой объект (в широком смысле слова, любые данные) или функцию. Гарантируется, что любые два нулевых указателя равны между собой. Разыменовывание нулевого указателя является операцией с неопределённым поведением.

Иначе говоря, реализация предоставляет специальное значение — константу нулевого указателя, которую можно присвоить любому указателю и такой указатель при сравнении не будет равен любому «корректному» указателю. То есть, можно считать, что нулевой указатель не содержит корректный адрес в памяти.

Содержание

Использование

Нулевые указатели придуманы как удобный способ «отметить» указатели, которые заведомо не указывают на корректный адрес в памяти. Например, при объявлении указателя как автоматической переменной его значение не определено. Чтобы отметить, что этот указатель ещё не содержит корректный адрес в памяти, такому указателю присваивают константу нулевого указателя:

Хорошим стилем программирования является присваивание указателю после освобождения памяти, на которую он ссылался, нулевого указателя. Кроме этого, применение обнуления указателей актуально для безопасности освобождения памяти: операция delete в C++ (free в Си) безопасна для нулевого указателя. Например:

в то время как в таком варианте ошибки не будет

Разыменовывание нулевых указателей

Разыменовывание нулевого указателя является операцией с неопределённым поведением. На реализацию не накладывается никаких ограничений: может произойти, например, обращение к памяти, не предназначенной для использования данной программой (то есть при чтении будет считан «мусор», а при записи — значение будет записано в область памяти, не принадлежащую программе). Например, в DOS запись по нулевому адресу затрёт как минимум нулевой вектор прерываний, так что следующий вызов int 0 приведёт, скорее всего, к зависанию системы. Однако чаще всего это приводит к ошибке времени выполнения (если в операционной системе реализована защита памяти и доступ в невыделенную процессу память блокируется). Например, в Windows 9x сообщение «Общая ошибка защиты» — «Программа выполнила недопустимую операцию и будет закрыта» (англ.  general protection fault, GPF ) выдаётся чаще всего в тех случаях, когда программа обращается в память по некорректному (в том числе неинициализированному или уже освобождённому) указателю. В Unix-подобных операционных системах в таких ситуациях процесс получает сигнал SIGSEGV и его обработчик выводит сообщение «Segmentation fault».

Нулевые указатели в C++

В отличие от классического Си в C++ значение пустого указателя предопределено стандартом языка и всегда равно 0 (целочисленному нулю, приведённому к типу «указатель»). Поэтому в программах на C++ не только возможно, но и рекомендуется использовать значение 0 вместо NULL [1] , однако некоторые программисты считают, что это ухудшает читаемость исходного кода. В стандарте C++11 для обозначения нулевого указателя добавлено новое ключевое слово nullptr [2] [3] .

См. также

Примечания

  1. Страуструп Б. 5.1.1 «Ноль» // Язык программирования C++. Специальное издание = The C++ programming language. Special edition. — М .: Бином-Пресс, 2007. — 1104 с. — ISBN 5-7989-0223-4
  2. JTC1/SC22/WG21 — The C++ Standards CommitteeSC22/WG21/N2431 = J16/07-0301 «A name for the null pointer: nullptr»   (англ.) (PDF). JTC1.22.32. The C++ Standards Committee (2 October 2007). Архивировано из первоисточника 11 февраля 2012.Проверено 4 октября 2010.   (англ.)
  3. Scott Meyers, Summary of C++11 Feature Availability in gcc and MSVC, 16 August 2011

Ссылки

      (англ.)   (англ.)
  • C++
  • Язык программирования Си

Wikimedia Foundation . 2010 .

Полезное

Смотреть что такое «NULL (Си)» в других словарях:

Null — (de) … Kölsch Dialekt Lexikon

null — null … Hochdeutsch — Plautdietsch Wörterbuch

Null —  Pour le musicien japonais, voir Kazuyuki K. Null. NULL est un mot clef présent dans de nombreux langages informatiques, et qui désigne l état d un pointeur qui n a pas de cible ou d une variable qui n a pas de valeur. La notion de valeur ou … Wikipédia en Français

Null — may refer to: Contents 1 In computing 2 In art 3 In mathematics 4 In science 5 People … Wikipedia

Null — «Null» redirige aquí. Para otras acepciones, véase Null (desambiguación). El término null o nulo es a menudo utilizado en la computación, haciendo referencia a la nada. En programación, null resulta ser un valor especial aplicado a un puntero (o… … Wikipedia Español

null — [nʌl] adjective [only before a noun] 1. STATISTICS a null effect, result etc is one that is zero or nothing 2. LAW another name for null and void: • Their suit also asks the court to declare null the buyer s shareholder rights plan. * * * … Financial and business terms

NULL (Си и Си++) — NULL в языках программирования Си и C++ макрос, объявленный в заголовочном файле stddef.h (и других заголовочных файлах). Значением этого макроса является зависящая от реализации константа нулевого указателя (англ. null pointer constant).… … Википедия

null — / nəl/ adj [Anglo French nul, literally, not any, from Latin nullus, from ne not + ullus any]: having no legal or binding force: void a null contract Merriam Webster’s Dictionary of Law. Merriam Webster. 1996 … Law dictionary

Null — Null, a. [L. nullus not any, none; ne not + ullus any, a dim. of unus one; cf. F. nul. See , and , and cf. .] 1. Of no legal or binding force or validity; of no efficacy; invalid; void; nugatory; useless. [1913 Webster] Faultily… … The Collaborative International Dictionary of English

Null-O — is a 1958 science fiction short story by Philip K. Dick. This rather brief story examines the concept of totally unempathic and logical humans ( Null O s) in an obvious parody of the plot and concepts of The Players of Null A by A. E. van Vogt.… … Wikipedia

Null — Sf std. (16. Jh.) Entlehnung. Entlehnt aus l. nulla gleicher Bedeutung, feminine Substantivierung von l. nullus keiner . Dieses ist eine Lehnbedeutung von arab. ṣifr, das ebenfalls Null und leer bedeutet und das seinerseits ai. śūnya Null, leer… … Etymologisches Wörterbuch der deutschen sprache

5 . Null Pointers

For each pointer type, C defines a special pointer value, the null pointer, that is guaranteed not to point to any object or function of that type. (The null pointer is analogous to the nil pointer in Pascal and LISP.) C programmers are often confused about the proper use of null pointers and about their internal representation (even though the internal representation should not matter to most programmers). The null pointer constant used for representing null pointers in source code involves the integer 0, and many machines represent null pointers internally as a word with all bits zero, but the second fact is not guaranteed by the language.

Because confusion about null pointers is so common, this chapter discusses them rather exhaustively. (Question 5.13- 5.17 are a retrospective on the confusion itself.) If you are fortunate enough not to share the many misunderstandings covered or find the discussion too exhausting, you can skip to question 5.15 for a quick summary.

Q 5.1 악명높은 `널 포인터’란 게 도대체 뭔가요?

Answer 언어 정의에 의하면 각각의 포인터 타입에 대해, 특별한 값이 — 널(null) 포인터 — 있어서, 다른 포인터 값들과는 구별되며, 어떤 오브젝트나 함수를 가리키는 포인터와는 항상 구별되는 포인터를 말합니다. 즉, 주소를 리턴하는 & 연산자는 절대로 널 포인터를 만들어 낼 수 없으며, 실패하지 않는 한 malloc() 함수도 널 포인터를 리턴하지 않습니다 ( malloc() 은 실패할 경우, 널 포인터를 리턴합니다. 그리고 이 것이 널 포인터의 쓰임새 — “할당되지 않은” 또는 “어떠한 것도 가리키지 않는”을 의미하는 특별한 포인터로 쓰이는 것 — 중 하나입니다.)

널 포인터와 초기화되지 않은 포인터 5 . 1 와는 개념상 완전히 다릅니다. 널 포인터는 어떠한 오브젝트나 함수도 가리키지 않는 포인터이고, 초기화되지 않는 포인터는 어떤 값을 가지는 지 모르므로, 아무 오브젝트나 가리킬 수 있는 포인터입니다. 질문 1.30, 7.1, 7.31을 참고하기 바랍니다.

위에서 설명한 것처럼, C 언어는 각각의 포인터 타입에 따라 널 포인터가 존재합니다. 그리고 널 포인터의 실제 값은 각 타입에 따라 서로 다를 수 있습니다. 컴파일러가 각 타입에 따른 실제 값으로 변경해 주기 때문에 프로그래머들은 각 타입에 따라 서로 다른 널 포인터의 내부적인 값을 알 필요가 전혀 없습니다 (질문 5.2, 5.5, 5.6을 참고).

Q 5.2 프로그램에서 어떻게 널 포인터를 쓰나요? Answer 널 포인터 상수를 ( null pointer constant ) 이용합니다. 언어 정의에 따라, 포인터가 쓰일 곳(context)에 상수 0을 — 좀 더 정확히 말해서, 0을 가지는 정수 상수 수식 5 . 2 을 — 쓰면 컴파일할 때 자동으로 널 포인터로 변경됩니다. 즉, 초기화나, 대입, 비교할 때, 한쪽이 포인터 타입의 변수나 수식일 경우, 다른 쪽의 0은 컴파일러가 자동으로 널 포인터로 바꾸어 준다는 뜻입니다. 컴파일러는 이 상수 0을 실제 널 포인터 값으로 바꾸어 줍니다. 따라서 다음과 같은 코드는 전혀 문제될 것이 없습니다 (질문 5.3 참고):

덧붙여 질문 5.3도 참고하시기 바랍니다.

그러나, 함수의 인자로 포인터를 전달할 경우, 포인터가 쓰일 곳(pointer context)으로 인식하지 못하고, 단순히 정수 0으로 인식할 가능성이 있습니다. 이럴 때에는 널 포인터라는 것을 강제적으로 캐스팅을 써서 알려 주어야 합니다. 예를 들어, UNIX 시스템 콜인 execl 은 가변 인자 리스트 5 . 3 를 받습니다. 이 함수는 인자의 끝을 알리기 위해서 널 포인터를 마지막으로 전달해야 합니다. 즉:

마지막 인자의 (char *) 캐스팅이 생략될 경우, 컴파일러는 이를 널 포인터로 인식하지 못하고 단순히 정수 0으로 인식합니다. (대부분의 UNIX 매뉴얼은 이 부분을 잘못 설명하고 있으니 주의해야 합니다. 덧붙여 질문 5.11도 참고하시기 바랍니다.)

함수의 프로토타입(prototype)이 있을 경우, 인자 전달은 대입(assignment) 연산으로 인식되기 때문에, 캐스팅을 할 필요가 없습니다. 왜냐하면 함수 프로토타입이 컴파일러에게 적절한 타입이 무엇이라는 것을 알려주기 때문입니다. 따라서 단순히 0만 전달해도, 컴파일러가 알아서 널 포인터로 바꾸어 줍니다. 그러나 가변 인자 리스트를 쓰는 함수의 인자는 프로토타입을 알더라도, 각각의 인자에 대한 타입을 알 수 없으므로 이런 함수의 인자로 쓰인 널 포인터에는 반드시 캐스팅을 써 주어야 합니다. (질문 15.3을 참고하시기 바랍니다.) varargs 함수에 쓰일 것을 대비하고, 함수 프로토타입이 없을 경우도 대비하고, ANSI 호환이 아닌 컴파일러에 쓰일 것을 대비하기 위해 널 포인터 상수 0에 항상 캐스팅을 하는 것이 혼동되지 않고 안전할 수 있습니다.

아래 표는 널 포인터 상수(0)를 그대로 써도 좋은 경우와, 그렇지 않는 경우에 대한 상황을 알려줍니다:

Q 5.3 포인터가 널 포인터인지 비교하기 위해 “ if (p) ”라고 쓰는 것이 안전한가요? 만약 널 포인터의 실제 값이 0이 아닐 경우에는 어떻게 되는 건가요? Answer 항상 안전합니다. C 언어에서 불리언(boolean) 값이 필요할 때 (예를 들어, if , while , for 그리고 do 와 같은 문장이나, && , || , ! 그리고 ?: 와 같은 연산자에서), 거짓은 0을 의미하며, 참(true)은 0이 아닌 값을 의미하게 됩니다. 따라서 다음과 같이 쓰게 되면: 실제로 ` expr ‘이 무엇이든지, 컴파일러는 위의 코드를 다음의 코드와 같은 것으로 봅니다. 따라서 ` expr ‘을 주어진 ` p ‘로 바꾸면, ` if (p) ‘가 ` if (p != 0) ‘이 됩니다. 그리고 이 수식은 비교를 하는 문맥(comparison context)이기 때문에, 컴파일러는 0이 널 포인터 상수라는 것을 알고, 실제 널 포인터 값으로 변경해줍니다. 크게 특별한 기술을 사용한 것도 아니고, 컴파일러는 두 경우 모두 같은 코드를 만들어 냅니다. 여기에서 실제 널 포인터의 값이 0인지 아닌지는 전혀 문제되지 않습니다.

다음과 같이 불리언 부정(not) 연산자인 ! 를 쓰는 것은:

다음과 같이 쓰는 것과 완전히 같습니다:

또는 다음과 같이 쓸 수 있습니다:

따라서, 다음과 같이 쓰는 것은:

다음과 같이 쓰는 것과 같습니다:

줄여서 (abbreviation) if (p) 로 쓰는 것은 전혀 문제될 것이 없습니다. 그러나 어떤 사람들은 이런 식으로 코딩하는 것이 나쁜 습관이라고 말합니다 (물론 어떤 사람들은 좋은 습관이라고 말합니다. 질문 17.10을 참고하기 바랍니다).

덧붙여 질문 9.2도 참고하시기 바랍니다.

Q 5.4 그럼 NULL은 무엇이고 어떻게 정의되어( #define ) 있나요? Answer 스타일에 관한 문제이지만, 대부분의 프로그래머들은 프로그램에서 0을 직접 쓰지 않는 (왜냐하면 0 자체가 수치를 뜻하기도 하고, 널 포인터를 뜻하기도 하기 때문에) 경향이 있습니다. 대신에, 전처리기(preprocessor) 매크로인 NULL 을 씁니다. 이 매크로는 <stdio.h> 와 <stddef.h> 를 포함한 여러 헤더 파일에 정의되어 있으며, 실제로 0으로 정의되어 있으며, 대개는 (void *) 로 캐스팅되어 있습니다 (질문 5.6 참고). 따라서 정수 0과 널 포인터 상수인 0을 쉽게 구별하기 위해, 널 포인터가 오는 곳에 NULL 을 사용합니다.

NULL 을 쓰는 것은 단순히 스타일적인 문제입니다; 전처리기가 NULL 을 0으로 바꾸어주므로, 컴파일러가 볼 때에는 모두 0으로 보게 됩니다. 따라서 함수 인자로 사용할 경우에는 0을 사용할 때와 마찬가지로 NULL도 캐스팅을 해줘야 할 필요가 있습니다.

질문 5.2의 표에서 0 대신에 NULL을 그대로 쓸 수 있습니다 (캐스팅하지 않는 NULL은 캐스팅하지 않는 0과 같기 때문입니다).

그러나 NULL은 반드시 포인터가 쓰이는 문맥에서만 쓰여야 합니다. 질문 5.9를 참고하기 바랍니다.

Q 5.5 널 포인터 값으로 0이 아닌 비트를 포함하는 값을 내부적으로 사용하는 시스템에서는 NULL 이 어떻게 정의되어 있나요?

Answer 다른 시스템과 똑같습니다. NULL은 시스템이나 컴파일러에 상관없이, 항상 0 또는 ((void *)0) 으로 정의되어 있습니다 (질문 5.4를 참고하기 바랍니다).

프로그래머가 널 포인터를 쓸 경우, 0을 쓰던지 NULL을 쓰던지에 상관없이, 컴파일러가 실제 컴퓨터의 내부적인 널 포인터 값으로 만들어 줍니다. (다시 말하지만, 컴파일러는 0이 포인터가 쓰일 곳에 쓰인 경우, 알아서 널 포인터로 바꾸어 줍니다. 질문 5.2 참고) 그렇기 때문에, 실제 널 포인터가 0이 아닌 다른 값을 갖는 시스템에서 NULL 을 0으로 정의한 것은 당연합니다. 컴파일러는 0이 포인터가 쓰일 곳에 쓰인 경우, 항상 그 시스템에 맞는, 올바른 널 포인터 값을 만들어 내야 합니다. 상수 0은 널 포인터 상수이며, NULL 은 단순히 같은 것을 의미하는 또다른 이름일 뿐입니다. (질문 5.13 참고)

C 표준 4.1.5장을 보면, NULL 에 대해서 “expands to an implementation-defined null pointer constant,”라고 표현한 문장이 있습니다. 즉, 어떤 형태의 0을 쓰던지, void * 캐스트를 쓸 것인지는 컴파일러가 결정합니다; 질문 5.6, 5.7 참고. 여기에서 “implementation-defined”란 용어가 NULL 이 0이 아닌, 내부적으로 쓰이는 널 포인터 값으로 쓰인다는 것을 뜻하지는 않습니다.

덧붙여 질문 5.2, 5.10, 5.17도 참고하시기 바랍니다. References [ANSI] § 4.1.5
[C89] § 7.1.6
[ANSI Rationale] § 4.1.5

Q 5.6 NULL이 다음과 같이 정의되어 있다면:

함수 인자로 NULL을 전달할 때, 캐스팅하지 않아도 되지 않을까요?

Answer 일반적으로, 꼭 캐스팅해야 합니다. 어떤 컴퓨터들은 포인터의 타입에 따라, 포인터의 내부 표현 방식이 다릅니다. 따라서 문자를 가리키는 포인터가 필요한 곳에 NULL을 그냥 쓰는 것은 문제가 없으나 (왜냐하면 위에서 char 에 대한 포인터로 캐스팅을 했기 때문), 다른 타입을 가리키는 포인터가 필요한 곳에 그냥 쓰는 것은 문제가 발생할 수 있습니다. 따라서, FILE *fp = NULL; 이 제대로 동작하지 않을 수도 있습니다. 그렇기 때문에, 반드시 적당한 타입의 포인터로 캐스팅해 주어야 합니다.

그러나 ANSI C는 NULL을 다음과 같이 정의하는 것을 허락하고 있습니다 5 . 4 :

그러나 NULL을 위와 같이 정의하는 것은 NULL을 잘못쓰는 문제를 어느 정도 (모든 포인터가 같은 내부 표현 방식을 가진 경우에만) 해결해 줄 수 있습니다. (ASCII NUL 문자가 필요한 경우, 질문 5.9 참고) 덧붙여 질문 5.7도 참고하시기 바랍니다.

최근에 나온 “flat” 메모리 구조를 가진 시스템에 익숙해져 있는 프로그래머라면 이러한 “타입에 따라 서로 표현 방식이 다른 포인터”라는 개념이 낯설 것입니다. 질문 5.17을 참고하기 바랍니다. References [ANSI Rationale] § 4.1.5

Q 5.7 제가 쓰는 시스템에서 NULL 은 0L 로 정의되어 있습니다. 왜 그럴까요? Answer 어떤 프로그래머들은 포인터가 쓰일 곳이 아닌 곳에 널 포인터를 쓰거나, 또는 캐스팅 없이 쓰는, 부주의한 실수를 저지릅니다. (이런 경우, 항상 동작한다고 보장할 수 없습니다. 질문 5.2, 5.11 참고) 정수보다 큰 크기를 갖는 포인터를 쓰는 시스템에서는 (예를 들어 PC 호환 “large” model을 쓰는 경우; 질문 5.17 참고) NULL 을 0L 로 정의하는 것이, 몇가지 에러를 잡는데 도움을 줍니다. (어쨌든, 0L 도 완벽한 NULL 의 정의가 될 수 있습니다. 왜냐하면 “integral constant expression with value 0”에 속하기 때문입니다.) Whether it is wise to coddle incorrect programs is debatable; 질문 5.6과 Chapter 17 참고. References [ANSI Rationale] § 4.1.5
[H&S] § 5.3.2 pp. 121-2

Q 5.8 함수 포인터 값으로 NULL 을 쓰는 것은 괜찮나요? Answer 좋습니다. (그러나 질문 4.13을 참고하기 바랍니다.)

Q 5.9 NULL 과 0이 널 포인터 상수로서 완전히 같다면 도대체 어떤 것을 써야하는 거죠?

Answer 대부분의 프로그래머들은 포인터가 쓰일 곳에서는 반드시 NULL 을 써야하는 것으로 믿고 있습니다. 다른 사람들은 NULL 이라는 매크로로 0을 가리는 것이 오히려 더 혼동을 가져온다고 생각하고 무조건 0을 쓰는 것을 선호하기도 합니다. 그렇지만 이 질문에는 어떠한 것도 완전한 해답이 되지 못합니다. (질문 9.2와 17.10을 참고하기 바람) C 프로그래머라면 포인터 문맥에서 NULL과 0을 마음대로 쓸 수 있다는 것을 알아야 합니다. 그리고 단지 0만을 쓰는 것도 완벽하다는 것도 알아야 합니다. (0하고는 달리) 포인터가 올 수 있는 곳이면, NULL을 쓰는 것은 좋습니다. 그러나 프로그래머가 (NULL을 0과 다른 것으로 취급하거나 컴파일러에서 특별하게 취급한다고 생각하는 등) 포인터 0과 정수 0을 구별하는데 NULL을 썼느냐 안 썼느냐로 판단하는 것은 좋지 않습니다.

포인터가 쓰일 곳에서만, NULL 과 0이 같다는 것을 잊어서는 안됩니다. 포인터가 쓰이지 않는 곳에서 NULL 을 쓰는 것은, 만약 제대로 동작한다 할지라도, 쓰면 안됩니다. 왜냐하면 그럴 경우, 잘못된 스타일에 관한 메시지가 발생하기 때문입니다. (게다가 ANSI는 NULL을 ((void *)0) 으로 정의할 수 있도록 하고 있으므로), 포인터가 쓰일 수 없는 곳에 NULL을 쓸 수 없는 시스템도 있습니다. 특히 ASCII null 문자 (NUL)이 쓰일 곳에 NULL을 쓰면 안됩니다. 꼭 매크로를 써야 한다면 다음과 같이 따로 정의해서 쓰는 것이 더 낫습니다:

Q 5.10 그렇지만 NULL의 값이 (0이 아닌 다른 값으로) 변경될 때를 (내부적으로 0이 아닌 다른 값을 쓰는 컴퓨터) 대비해서 0 대신에 NULL을 쓰는 것이 더 좋지 않을까요? Answer 아닙니다. (NULL을 쓰는 것이 바람직할 수 있지만, 이런 이유에서가 아닙니다.) 일반적으로 심볼릭(symbolic) 상수가 쓰이는 것은, 실제 값이 변경될 경우를 대비해서 쓰는 경우가 많지만, 0의 자리에 NULL이 쓰이는 것은 이런 이유가 아닙니다. 다시 말하지만 언어 자체가 (포인터 문맥에서) 0이 널 포인터를 만들어 낸다고 정의하고 있습니다. NULL을 쓰는 것은 단순히 스타일에 관한 문제입니다. 질문 5.5, 9.2를 참고하기 바랍니다.

Q 5.11 NULL 을 쓰지 않은 경우, 아예 동작하지 않는 프로그램을 만들어 내는 컴파일러를 쓴 적이 있습니다. Answer 이식성이 없게 코드를 작성한 것이 아니라면, 컴파일러가 고장난 것입니다. 아마 질문 5.2와 같이 이식성이 없는 코드를 다음과 같이 썼는지 확인해 보기 바랍니다:

NULL 을 ((void *)0) 으로 정의하고 있는 (질문 5.6 참고) 컴파일러에서 이 코드는 동작합니다 5 . 5 . 그러나, 포인터와 정수가 다른 크기나 표현 방식을 쓰는 시스템이라면, 아래와 같은 코드는 (똑같이 잘못된 것이면서) 동작하지 않을 수 있습니다:

이식성이 뛰어난, 좋은 코드는 다음과 같습니다:

캐스트를 써서, 위 코드는, 시스템에서 포인터와 정수가 크기가 다르거나, 내부 표현 방식이 다르더라도 동작하며, NULL 의 정의가 어떤 식으로 되어 있느냐에 상관없이 동작합니다. (질문 5.2에서 NULL 대신에 0을 쓴 코드는 같은 이유로 올바른 코드입니다; 덧붙여 질문 5.9도 참고하시기 바랍니다.)

Q 5.12 다음과 같은 매크로를 써서 널 포인터가 적절한 타입이 되도록 하고 있습니다: 이게 좋은 습관일까요?

Answer 이 트릭은 매우 인기있고 매력적이긴 하지만, 크게 도움이 되지 않습니다. 이 방법은 대입이나 비교에서는 필요 없습니다. (질문 5.2 참고) 게다가 추가적으로 타이핑하는 수고가 필요합니다. 또, 이 코드의 개발자가 널 포인터에 대하여 잘 이해 못하고 있다는 것을 알려주며, 다른 개발자는 위 매크로를 정의한 부분, 사용한 부분, 그리고 포인터를 쓰는 모든 코드를 다시 검사해야 안전할 것입니다. 질문 9.1과 10.2도 보시기 바랍니다.

Q 5.13 약간 헷갈립니다. NULL은 0인 것이 보장되어 있고, 널 포인터는 아니라고 한 것 같은데 맞습니까?

Answer 일반적으로 “null”과 “NULL”이 혼용되어 쓰이긴 하지만 다음 사항은 알아두셔야 합니다:

  1. 널(null) 포인터에 대한 개념은 질문 5.1에 정의되어 있습니다.
  2. 널 포인터가 실제로 갖게 되는 내부적인 값은 각각의 타입에 따라 다르며 특정 비트가 0이 아닌 값일 수도 있습니다. 실제 널 포인터의 값은 컴파일러 제작자나 필요한 것이지 C 프로그래머는 널 포인터가 실제 어떠한 값인지 전혀 알 필요가 없습니다.
  3. 널 포인터 상수는 정수 상수 0 5 . 6 입니다 (질문 5.4 참고).
  4. NULL 매크로는 0으로 정의( #define )되어 있습니다. (질문 5.4 참고).
  5. ASCII 널 문자 (NUL)는 모든 비트가 0인 값으로 널 포인터와는 이름만 같을 뿐, 전혀 상관이 없습니다.
  6. 널 문자열(null string)은 빈 문자열 ( «» )을 나타내는 다른 말로, C 언어에서 `널 문자열’이라는 용어를 쓰는 것은 혼동을 가져옵니다. 비어있는 문자열은 널 문자(‘ \0 ‘)를 말하는 것이지, 널 포인터와는 상관 없습니다.

이 글에서는 “널 포인터(null pointer)”라는 용어를 위의 1번의 목적으로 사용합니다. 3 번의 목적으로는 0이나 “널 포인터 상수”라는 표현을 쓰며, 4 번의 목적으로 “NULL”을 사용합니다 5 . 7 . References [H&S] § 1.3 p. 325
Through the Looking-Glass , chapter VIII.

Q 5.14 왜 이토록 널 포인터에 대한 논쟁이 많은 것인가요? 왜 이런 질문이 자주 나오죠?

Answer C 프로그래머들은 전통적으로 저수준 기계의 구현 방식(machine implementation)에 대해 좀 더 많은 것을 알기 원하는 경향이 있습니다. 문제는 널 포인터가 소스 코드와 기계 자체에 다 쓰이는 개념이지만, 실제 기계에서는 0이 아닌 다른 값으로 표현될 수 있다는 데에 있습니다. 전처리기 매크로인 NULL 을 쓰는 것이 나중에 변경될 소지가 있는 것처럼 보이는 것도 문제가 됩니다. 또 “ if (p == 0) ”에서, 사실은 0을 널 포인터로 생각하고 비교하는 것이지만 p 를 정수형으로 바꾸고 비교하는 것처럼 보일 수도 있습니다. 마지막으로 여러 의미를 가지는 (질문 5.13 참고) “null”이라는 용어를 건성으로 보는 경향이 있습니다.

C 언어에서 이런 혼동을 없애기 위한 방법으로, 널 포인터 용으로 (Pascal의 nil 과 같은) 키워드(keyword)를 만들어 썼다면 좋은 효과를 얻었을 것이라고 생각합니다. 그러면 컴파일러는 “ nil ”을 적절한 널 포인터로 바꾸어 줄 수 있을 것이며, 널 포인터가 올 수 없는 곳에서는 경고를 만들어 낼 수 있을 것입니다. 그러나 현재 C 언어에서 널 포인터를 나타내는 키워드는 “ nil ”이 아니라 “0”입니다. 그리고 널 포인터가 올 수 없는 곳에 0이 쓰이면, 에러가 발생하는 것이 아니라 정수 0으로 해석되며, 널 포인터가 와야 할 자리에 캐스팅하지 않는 0이 오게 되면, 동작하지 않을 수도 있다는 것이, 우리의 이상과는 다릅니다.

Q 5.15 매우 헷갈리는 군요. 널 포인터에 관한 이 모든 사항을 쉽게 알 수 없나요?

  1. 소스 코드에서 널 포인터 상수가 필요할 경우, 0이나 “NULL”을 씁니다.
  2. 0 또는 NULL이 함수 인자로 쓰일 경우에는, 그 함수 인자 타입에 맞는 포인터로 캐스팅해서 사용합니다.

Q 5.16 이런 혼동을 없애기 위해, 단순히 널 포인터가 내부적으로 0으로 나타내어 진다고 아예 못 박아 놓는 것이 좋지 않을까요?

Answer 다른 이유없이 그렇게 하는 것은 좋지 않은 생각입니다. 왜냐하면 어떤 기계에서는 널 포인터를 쓸 경우, 자동적으로 하드웨어 트랩(trap)이 발생하도록 해 놓았기 때문에 실제로 널 포인터가 0이 아닌 다른 값으로 쓰일 수 있기 때문입니다.

게다가 널 포인터에 대해 잘 이해하기 위해, 실제로 내부적으로 표현되는 널 포인터의 값을 (0인지 아닌지에 대해) 알 필요가 전혀 없습니다. 단순히 널 포인터가 내부적으로 0으로 표현된다고 생각한다고 해서, 코드를 작성하기 쉬워지는 것도 아닙니다. (잘못된 calloc() 에 대한 설명을 질문 7.31에서 참고하기 바랍니다.)

그리고 널 포인터가 0이라고 해도 포인터의 크기가 타입에 따라 달라질 수 있기 때문에 여전히 함수 호출에서 캐스팅을 해야 합니다. (만약 질문 5.14에서 말한 것처럼 “ nil ”이 널 포인터로 쓰일 수 있다면 널 포인터가 0인지 아닌지에 대한 논쟁 자체가 의미없는 것이 될 것입니다.)

Q 5.17 (심각하게) 정말로, 0이 아닌 비트 패턴을 사용하는 기계나 각각의 타입에 따라 다른 형태의 포인터를 쓰는 컴퓨터가 있나요?

Answer Prime 50 시리즈는 적어도 PL/I 언어에서 널 포인터를 나타내기 위해 세그먼트 07777, 옵셋 0을 사용합니다. 최근의 모델에서는 TCNP (Test C Null Pointer) 명령을 써서 (C 언어에서) 세그먼트 0, 옵셋 0을 사용합니다. 또 오래된 워드 주소를 쓰는(word-addressed) Prime 기계는 바이트 포인터 ( char * )보다 워드 포인터 ( int * )가 크기가 더 작습니다.

Data General사의 Eclipse MV 시리즈는 기계 수준에서 세 가지의 포인터 타입을 제공합니다 (워드, 바이트, 비트 포인터). C 언어에서는 두 가지 형태를 사용하며 char * 와 void * 는 바이트 포인터로, 나머지 포인터는 워드 포인터로 구현됩니다.

어떤 Honeywell-Bell 메인프레임에서는 널 포인터 값으로 06000을 사용합니다.

CDC Cyber 180 시리즈는 링(ring), 세그먼트, 옵셋 부분으로 이루어진 48 비트 포인터를 사용하며, (링 11의) 대부분의 사용자는 널 포인터로 0xB00000000000를 사용합니다. 오래된 CDC는 “1의 보수(one’s complement)” 방식을 사용하며, 잘못된 주소를 포함한 모든 데이터의 예외 상황에 모든 비트가 1인 수치를 사용합니다.

오래된 HP3000 시리즈는 위에서 소개한 다른 시스템처럼, char * , void * 타입에 대한 포인터와 나머지 포인터들을 바이트 어드레싱과 워드 어드레싱을 써서 구현하며, 두 어드레싱이 서로 다른 방식을 사용합니다.

Symbolics Lisp 컴퓨터에서는, (tagged architecture), 아예 수치로 표현되는 포인터를 제공하지 않습니다. C 널 포인터는 <NIL, 0> 으로 구현됩니다. (기본적으로 <object, offset> 을 사용함.)

8086 계열의 프로세서 (PC 호환) 에서는 `메모리 모델’에 따라 16 비트 데이터 포인터와 32 비트 함수 포인터를 쓸 수 있습니다. 또는 32 비트 데이터 포인터와 16 비트 함수 포인터를 쓸 수 있습니다.

어떤 64 비트 Cray 컴퓨터에서는 int * 를 한 워드의 하위 48 비트로 표현하며, char * 는 나머지 상위 16 비트를 옵셋으로 써서 표현합니다.

References [K&R1] § A14.4 p. 211

A null pointer should not be thought of as pointing at address 0, but if you find yourself accessing address 0 (either accidentally or deliberately), null pointers may seem to be involved.

Q 5.18 Run-time에 정수 0을 포인터로 캐스팅했을 때, 널 포인터 값으로 쓴다면 괜찮을까요? Answer 아닙니다. 오직 정수 상수 수식 0만이 ( constant integral expressions with value 0) 널 포인터가 되는 것이 보장되어 있습니다. 질문 4.14, 5.2, 5.19를 보기 바랍니다.

Q 5.19 시스템 주소 0에 위치해 있는 interrupt vector에 어떻게 접근할 수 있을까요? 포인터에 0을 대입한다면, 컴파일러가 이 것을 널 포인터로 해석해버릴텐데요. Answer 주소 0에 실제로 어떤 데이터가 있는지는 순전히 시스템에 의존적인 문제입니다. 따라서 시스템이 제공하는 여러 가지 기법을 쓰면 해결될 수 있을 것입니다. 시스템이 제공하는 문서를 참고하기 바랍니다. (그리고 Chapter 19도 참고하기 바랍니다.) 주소 0에 접근하는 것이 당연한 시스템이라면, 당연히 접근하기 쉬운 방법을 제공했을 것입니다. 몇가지 가능성을 생각해 보면:

  1. 단순히 포인터에 0을 대입한다. (동작한다는 보장은 없지만, 주소 0에 접근하는 것이 의미있는 시스템이라면, 동작할 것입니다.)
  2. 수치 0을 int 변수에 대입하고, 이 int 를 포인터로 변환합니다. (역시, 보장할 수는 없습니다.)
  3. union을 써서, 포인터의 모든 비트를 0으로 합니다:
  4. memset 을 써서 포인터의 모든 비트를 0으로 합니다:
  5. extern 변수나 배열을 선언하고: 어셈블리나 링커의 특별한 명령을 써서 이 심볼이 주소 0을 가리키도록 합니다.

Q 5.20 run-time에 “null pointer assignment”라는 에러가 발생합니다. 이게 무엇을 의미하나요? 또 어떻게 해결할 수 있죠? Answer 이 메세지는 MS-DOS 용 컴파일러가 자주 발생시키는 전형적인 포인터 에러 메시지입니다. 즉 널 (또는 초기화되지 않은) 포인터를 써서 잘못된 위치(대개 디폴트 데이터 세그먼트의 옵셋 0)에 어떤 데이터를 쓰려할 때 발생합니다.

어떤 디버거들은 데이터 와치포인트(watchpoint)를 주소 0에 설정할 수 있도록 해 줍니다. 또는 아예 주소 0 근처의 약 20 바이트 정도를 다른 곳에 복사해두고 주기적으로 비교해서 변경되었는지를 검사할 수도 있습니다. 질문 16.8을 참고하기 바랍니다.

All rights reserved. Copyright © 2004-2006 Seong-Kook Shin (신성국)
Return to my homepage

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

그냥 0을 써도 좋은 경우: 캐스팅이 반드시 필요한 경우:
초기화(initialization)
대입(assignment)
비교(comparison)