今天在微博上看到一段小程序,博主问会不会core,如果core的话,会是在哪一行。程序代码如下。
#include <stdio.h>
struct str
{
int len;
char s[0];
};
struct foo
{
struct str *a;
};
int main()
{
struct foo f = {0};
if(f.a->s)
{
printf(f.a->s);
}
return 0;
}
既然博主这么问了,那肯定是有错误的了,但是功力浅薄,感觉有问题,却道不出所以然来。 所以就实际上编译运行了一遍。结果还真是错误了。
段错误 (核心已转储)
gdb单步调试发现是在printf()这个函数执行时,程序挂掉了。再从头执行一遍,到printf时,打印出各个变量的值,发现f.a->s这个值是0x04, 那么根据printf的声明
int printf(const char *format, ...);
把0x04这个值当成是一个字符串到开头地址,对这个值进行解引用的话,当然会有错误。(非法访问吗?)
为什么f.a->s这个值会是0x04呢? 首先看看 f.a 的值, f = {0} 就是给a赋值的,所以 f.a = 0x00。
再看看 str 这个结构体的定义,s 是一个数组,而数组名是数组的开头地址,那么就是说f.a->s是一个地址,而这个地址则刚好是s在str中的偏移量。
len这个变量是int,所以s的地址是在a的地址(0x00)的基础上加上4,所以f.a->s = 0x04。
通过gdb的调试。f.a->s是可以看到输出的,但是f.a->len确实非法的。
(gdb) p f.a->s
$1 = 0x4 <Address 0x4 out of bounds>
(gdb) p f.a->len
Cannot access memory at address 0x0
(gdb)
如果把str的结构改成如下
struct str
{
int len;
char *s;
};
对 f.a->s 的访问也是不合法的。
所以,我感觉要点就是:
数组名是数组的开头地址。
=====================================================
这个跟offsetof宏有点相似。
#define offsetof(s, m) (size_t)&(((s *)0)->m)
这个宏是要取得变量m在结构体s中的偏移量。
1. 将0强制转换成该结构体的指针: (s *)0;
2. 取得该成员变量:((s *) 0)->m
3. 取地址并转换成size_t类型:(size_t) &(((s *) 0)->m)
写到这里突然又有一个问题没想明白了:为什么这个宏中对0x00的解引用((s *) 0)->m是合法的?而在f.a->len中,a的值也为0,那么为什么却又不合法呢?
坐等coolshell的解释。。