原题目为《程序员面试宝典》第7章面试题9:
struct S{
int i;
int *p;
};
main()
{
S s;
int *p =&s.i;
p[0] = 4;
p[1] = 3;
s.p = p;
s.p[1] = 1;
s.p[0] = 2;
}
问程序会在哪一行崩溃?
第一次做这道题时我崩溃了:),脑子里全是糊的!好在VS提供了调试功能:
进行到图中步骤后,注意p指向的地址就是&s也就是&s.i,经过了p[0]=4;p[1]=3赋值后,可以看到是s.p指向的是未声明的空间0x00000003,而s.i的值就是4;
在经过了s.p = p;后:
可以发现s.p指向的地址也变成了0x00c7fd60,对s.p[1]=1;实际上就是对s.p赋值1:
显然,这时候是无法再执行s.p[0] = 2;了因为0x00000001空间根本就未声明。
*********************************************************************************************************************************************
原本这个问题就这么解决了,可我今天复习时,在deepin15.6下跑了一下这个程序,居然能通过了......
但是,敏锐的我很快就意识到了问题所在:
因为在GDB调试下发现,结构体S s里int i和int *p的地址差了8而不是4,作为一个在面试中被sizeof坑过的boy,我马上就想到了deepin是64位系统啊,这样一个指针是8字节!
很明显,字节对齐的规则把这个致命的bug掩盖了。
在64位系统下,S中内存分布如下图所示:
1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
int i | int *p |
可以观察到&s和&s.p之间差了8个字节(0x000000079152ff630 - 0x000000079152ff628)。
在32位系统下致命的s.p[1]=1;在64位系统下成为了一句不痛不痒的赋值,接下来的s.p[0] = 2;也就可以执行了。
********************************************************************************************************************
那么,怎么样复现这个bug呢?
按照上述分析,我们可以这样做:
struct S {
int i;
int *p;
};
int main()
{
S s;
int *p = &s.i;
p[0] = 4;
p[1] = 3;
p[2] = 2;
s.p = p;
s.p[2] = 1;
s.p[0] = 2;
}
但是且慢!能不能有更有逼格的方式呢?
#pragma pack(push)
#pragma pack(4)
struct S {
int i;
int *p;
};
int main()
{
S s;
int *p = &s.i;
p[0] = 4;
p[1] = 3;
s.p = p;
s.p[1] = 1;
s.p[0] = 2;
}
#pragma pack(pop)
就是使用pragma pack改变对齐规则啦!