一段小代码

今天在微博上看到一段小程序,博主问会不会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) 


从上面的提示可以看出,struct str的实际大小其实是4 + 0 = 4。而 f.a->s的地址是0x04的话,已经是超过了struct的范围了。


如果把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的解释。。

你可能感兴趣的:(一段小代码)