你所不知道的C语言:指针篇(Null pointer)

Null pointer?

考虑以下程序 (null.c)

<code>int main() { return (NULL == 0); }		/<code>

用 gcc-7.2 编译:

<code>null.c:1:22: 'NULL' undeclared (first use in this function)
int main() { return (NULL == 0); }
                     ^~~~
null.c:1:22: note: each undeclared identifier is reported only once for each function it appears in/<code>

表示 NULL 需要定义,我们加上 #include 即可通过编译。那 main 的返回值是 0 还是 1 (或非零) 呢?

  • 根据 C99 规范 6.3.2.3

nothing requires the NULL pointer to have the numeric value of zero;

这点导致编程的歧义

  • C99 规范6.3.2.3 - 3

A “null pointer constant” does not have to be a “null pointer

空指针常数(null pointer constant)

  • 根据C11 standard 6.3.2.3 对空指针常数 (null pointer constant)的定义:

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

大意是说,空指针常数代表计算结果为零的整数常数表达式,如 0 或 0ULL,或是上述表达式强制转换成 void*, 如 (void*)(0U)。

NULL是用MACRO定义,并由实际环境决定(implementation-defined)空指针常数。

  • 对C来说,这两种写法都对:

#define NULL ((void *)0) // 一般C编译器常见的写法

#define NULL 0 // 也是符合定义的写法

((void*)0) 是空指针常数(null pointer constant),

而((int *)0) 是类型为整数指针的空指针(null pointer)**,这两者是不一样的。


比较明显的区别时,空指针常数可以被赋值给任何类型指针的左值

而空指针只能被赋值给同样类型指针的左值(若不同类型会有warning)

  • 以下是一个比较清楚的例子:
<code>int *a = 0;           // 沒问题,空指针常数
int *b = (int*) 0;    // 沒问题,右边是同类型的空指针
int *c = (void*) 0;   // C沒问题,右边是空指针常数,不过C++会挂
int *d = (long*) 0;   // 会有warning提示 (incompatible pointer type)
int *e = (int*) a;    // C不保证是空指针,C++会挂/<code>

只有常数表达式才能保证会转型成空指针,

执行期间才得到的零不保证可以成功转型成空指针

  • 有另一个例子:
<code>typedef void (*func)(void);   // func f  = void (*f)();
func a = 0;                   // 沒事,a是空指针常数
func b = (void *)0;           // C 沒问题,C++会挂
func c = (void *)(void *)0;   // C 沒问题,C++会挂/<code>
  • 根据C99 / C11 規格 6.3.2.3 - 3

“An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. 66) 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.”

在 caller 端转换数值为 pointer type 时,就会变为null pointer。

  • 延伸 C99/C11 規格 6.3.2.3 - 4

Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.

(void*)(uintptr_t) 0 本质上是个 null pointer,而 NULL 是 null ptr,以 C99 的观点。

  • C99 规范 6.7.8 Initialization

当通过 malloc() 建立的对象经由 free() 释放后,应該将指针设定为 NULL

避免 doubly-free

  • free() 传入 NULL 是安全的
你所不知道的C语言:指针篇(Null pointer)

两个 NULL 指针是否能比较?

  • 根据C99 规范 6.3.2.3 - 3

“An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. 55) 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.”

  • 先解释 compare unequal

“compare unequal” 意味着 “a == b is false”,换句话说:“a != b is true”

  • 接下來 C99 规范在 6.3.2.3 又说:

Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.

  • 对 pointer type 作比较,又是另外的情况
  • 根据 C99 规范6.5.8 - 5

“Comparison of pointers that do not point to the same object or to members of the same agregate is undefined.”

  • 需要注意的是 condition (传入 assert() 的参数也是) 根据 C99 规范是 “a scalar type”,而pointers 也是 scalar

C 语言 offsetof 的定义

定义了,offsetof 宏,用于取得 struct 特定成员 (member) 的位移量。传统的实现方式如下:

<code>#define offsetof(st, m) ((size_t)&(((st *)0)->m))/<code>

这对许多 C 编译器 (像是早期的 gcc) 可正确运行,但依据C99 规范,这是 undefined behavior。后来的编译器就不再通过该宏来实现,而改用 builtin functions,像是 gcc:

<code>#define offsetof(st, m) __builtin_offsetof(st, m)/<code>

Linux 核心延申 offsetof,提供 container_of ,作为反向的操作,也就是给予成员地址址和类型,反过来找 struct 起始地址:

<code>#define container_of(ptr, type, member) ({ \
    const typeof(((type *) 0)->member) *__mptr = (ptr); \
    (type *) ((char *)__mptr - offsetof(type,member) );})/<code>


分享到:


相關文章: