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 是安全的
两个 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>