通过空指针访问结构体成员的地址

3
为什么以下表达式不会产生(空指针)运行时错误?
typedef struct{
           int a,b,c;
          } st;

st obj={10,12,15};
st *ptr1=&obj;
st *ptr2=NULL;

printf("%d",*(int *)((char*)ptr1+(int)&ptr2->b));

3
这种操作的正确方法是使用<stddef.h>头文件中的offsetof宏。(offsetof通常是基于空指针算术实现的,但这是因为它是与特定编译器一起使用的C库的一部分,所以编写者知道编译器会处理它。) - aschepler
@aschepler 就是这样。 - user529758
4个回答

4
因为您在一个NULL指针上执行指针算术运算,这会引发未定义的行为 - 并且不一定会崩溃。

1
很好,但问题标记为[c],而不是[gcc-extensions]。 - aschepler
@ouah 在这个例子中,没有人对 void * 进行指针算术运算。 - user529758
@aschepler 但是也许提问者不知道GNU C和C之间的区别,所以才会问这个问题...如果他知道原因,就不会问这个问题了。 - pinkpanther

2
实际上,在GNU C中,当st *ptr2=NULL;时,&ptr2->b会产生结构体中数据成员"b"的字节偏移量,这里是4。

1

不幸的是,解引用空指针并不总是会崩溃!有时它只是愉快地继续前进,访问无效的内存!

你可能会看到输出值为“12”。当你解引用一个指针时,编译器所做的是添加一个偏移量以获取成员的地址。

int通常是4个字节长,因此如果ptr2的地址是10,那么ptr2->a也必须在地址10处,ptr2->b必须在14处,ptr2->c必须在地址18处。

由于ptr2是NULL,所以它加上4就能到达成员bNULL+4=4。然后你将4加到ptr1上并对其进行解引用,这会得到ptr1b成员!

但这是不可移植的,你不应该这样做!


&ptr2->b 等价于 &(ptr2->b),因为操作符 -> 的优先级高于 & 操作符。所以如果 ptr2 为空,则评估 ptr2->b 应该导致段错误。如果在相同的问题中没有使用 & 操作符,程序会崩溃。 - Sundara Raghavan

0

这是完全合法的用法,因为“取地址”运算符实际上意味着:“不要加载/存储变量的值,只需给我计算出来的地址以访问它。” 它在实际未定义行为被调用之前停止了访问。

这是编程中如此典型的习语,以至于它实际上被封装在offsetof()宏中(在Linux中广泛使用),并已经进入编译器本身以提供更有意义的错误消息。请参见http://en.wikipedia.org/wiki/Offsetof获取更多详细信息,尽管我不同意该文章对该声明合法性的看法。


谁以及为什么给它点了踩?难道这不是正确的答案吗?+1 - TT_ stands with Russia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接