假装异步加载中...
24 Oct 2012

让人费解的空指针

今天看到这样一个宏:

#define OFFSETOF(type, field) \
(((char*) &((type *)0)->field) - ((char*) (type *) 0))

乍一看,懵了,查看其使用方法:

struct thread {
	ulong_t stack_ptr;		/* saved stack pointer (this must be the first field!) */
	volatile u32_t num_ticks;       /* number of ticks thread has been running */
	void *stack;                    /* kernel stack */
	struct thread *parent;          /* parent thread */
	struct process *proc;           /* process the thread belongs to (null for kernel-only) */
	thread_state_t state;           /* state of thread in lifecycle */
	int exitcode;                   /* thread's exit code */
	int refcount;                   /* num threads that will wait for this one */
	struct thread_queue waitqueue;  /* wait queue for thread lifecycle events */
	DEFINE_LINK(thread_queue, thread);
};
OFFSETOF(struct thread, stack_ptr);

 

大致明白了type是一般是一个结构体,field是其结构体成员,这个宏的意思是:求结构体成员在结构体中的偏移。一个问题解决了,可是又来了一个问题:(type*)0不是一个空指针吗,为什么对其取field成员不会报错?

维基百科关于offset的说明,其中有一点是:the macro relied on the compiler being not especially picky about pointers; it obtained the offset of a member by specifying a hypothetical structure that begins at address zero.

大概意思是这个宏计算偏移是通过指定一个假想的从地址0开始的结构体

While this works correctly in many compilers, it has undefined behavior according to the C standard, since it involves a dereference of a null pointer (although, one might argue that no dereferencing takes place, because the whole expression is calculated at compile time).

大概意思是虽然这个宏在很多编译器里能正常运行,由于指定了空指针,按照C语言标准会有不可预期的结果出现,尽管这个表达式的值在编译时计算出来。

综上所述,我的理解是这个宏的正确性依赖于编译器,虽然它是在编译时计算。

最后,我对这里的空指针的理解,这里有空指针,它指向了内存地址为0的内存单元,虽然不可写,但是这个指针保存它的结构,也就是编译器对这个具有类型的空指针维护有它的符号表,可以通过这个指针计算出其成员地址,这个在编译时是可通过的,所以我们在&((type *)0)->field)时,没有任何问题,只是我们对这种内存单元进行读写操作,OS对其有保护,会抛出段保护错误。当然如果我们的程序运行在内核空间,那就难说了。

参考资料:
维基百科空指针:http://en.wikipedia.org/wiki/Pointer_(computer_programming)#Null_pointer
维基百科offset宏:http://en.wikipedia.org/wiki/Offsetof