C语言之指针,请前辈指教!

OphoneOu 2010-01-18 01:05:15
在C语言中,最难的是指针。

自以为看了一些大作后,很了解指针,其实我是菜鸟。希望前辈们赐教。
看<<C专家编程>>
(*((void *)())0)();
这句很简单,语法上是绝对没有问题的!
问题是,它绝对是错误的,原因是0这个地址invalid的。

为什么用0或者NULL来初始化一个指针?
其原因是在操作系统内部实现中,每个进程的地址空间从0到一定范围内,(Windows 平台是0~64K)是不合法的,
也就是说你无法使用这部分内存,不可读也不可写。所以
(*((void *)())0)();
在大部分主流操作系统绝对不可能运行起来!

好,进入我的问题!
Linux内核使用链表与数据分离方式来组织链表。

struct list_head {
struct list_head *next, *prev;
};
struct list_node {
struct list_node *next;
ElemType data;
};



在链表操作上有以下宏:

#define list_entry(ptr, type, member) container_of(ptr, type, member)
container_of宏定义在[include/linux/kernel.h]中:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
offsetof宏定义在[include/linux/stddef.h]中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


我们来看定义

offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

语法是绝对没有问题,但是和上面的分析同理。0在进程的地址空间是invalid的。
那么这个((TYPE *)0)->MEMBER使用应该跟上面的(*((void *)())0)();调用一样
应该在运行时错误,但是它没有发生运行时错误!为什么?

解释一下(TYPE *)0)->MEMBER
它将0地址强制"转换"为type结构的指针,再访问到type结构中的member成员。
语法没有问题,但是这个访问到type结构中的member应该运行时错误才对。
可是它没有错误!为什么?
...全文
199 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
thomasie 2010-02-01
  • 打赏
  • 举报
回复
我是小白,能不能解释的详细一些。(TYPE *)0到底是什么含义
OphoneOu 2010-01-18
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 pengzhixi 的回复:]
并没有对这个(TYPE *)0做什么违规的访问什么的。所以和前面说的完全是两码事。
[/Quote]
有道理。
xylicon 2010-01-18
  • 打赏
  • 举报
回复
和你上面的操作不一样吧。你上面那个是已经执行了0地址这个操作了。所以地址invalid。

而((type *)0)->member 只是把type* 映射到0这个地址,求出member的偏移值。正如2楼所说,这个预编译的时候已经得出结果了。所以运行时((type *)0)->member 只是一个数字 就是偏移值。
OphoneOu 2010-01-18
  • 打赏
  • 举报
回复
更正一个错误(*((void *)())0)();应该为(*(void (*)())0)();
pengzhixi 2010-01-18
  • 打赏
  • 举报
回复
并没有对这个(TYPE *)0做什么违规的访问什么的。所以和前面说的完全是两码事。
versaariel 2010-01-18
  • 打赏
  • 举报
回复
前两天看到过,理解了,今天再学一遍
pengzhixi 2010-01-18
  • 打赏
  • 举报
回复
offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
从你这个字面上就可以理解,是利用(TYPE *)0这个技巧来获得成员变量的偏移地址,和你说的那个情况不一样。
blackmash 2010-01-18
  • 打赏
  • 举报
回复
其实只是将地址0的memory,强制转化成你的结构,然后去拿member的“地址”而已。并未对member做什么操作。。
james_hw 2010-01-18
  • 打赏
  • 举报
回复
操作系统不会允许操作起始那部分地址,但是(size_t) &((TYPE *)0)->MEMBER)这个操作应该是由编译器来完成的,也就是说预编译阶段已经将其替换成数字了,操作系统看到的数字。
jack_wq 2010-01-18
  • 打赏
  • 举报
回复
upup

69,382

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧