今天在读 C 陷进与缺陷 一书时了解到数组边界赋值移除会覆盖其他变量的问题,书中例子这样写会导致覆盖i
然后死循环:
int i, a[10];
for (i = 1; i <= 10; i++)
a[i] = 0;
于是自己就尝试了一下,发现并没有发生死循环,调试了发现i
的地址在数组a
之前,于是又将声明顺序交换,依然是i
在前面。
然后自己再尝试这样:
int a[4]={1,2,3,4};
int m=0;
int n=0;
int b[4]={5,6,7,8};
int i=0;
int j=0;
调式发现打印的地址是
m: 0x7ffffe275a90
n: 0x7ffffe275a94
a[0]: 0x7ffffe275aa0
a[3]: 0x7ffffe275aac
i: 0x7ffffe275a98
j: 0x7ffffe275a9c
b[0]: 0x7ffffe275ab0
b[3]: 0x7ffffe275abc
即变量在内存中的顺序是 m->n->i->j->a[]->b[]
似乎是 gcc 先给 int 分配了内存然后再给 int 数组分配,随后修改了几次声明顺序依然是这样的规律。
Google 上查了下有人说并没有任何标准定义 C 语言中变量内存分配的顺序是按照代码的顺序来的。但是它确实是有这样一个规律,我也好像没有找到什么官方的解释变量究竟是按照怎么样的顺序在堆栈上分配的,以及为何要这样做。
在这里请教一下,不知道有没有人了解。
谢谢 7 楼的提醒还有缓冲区溢出,没想到这里来,加上 -fno-stack-protector
关闭 GCC 的栈溢出保护编译内存地址就顺序分配了。至于具体内存分配顺序是怎么优化的 GCC 文档上应该有写,有兴趣的可以去找找看,我也不继续详细了解了。
1
pagict 2018-07-18 17:21:10 +08:00
你把优化关了再试试呢
觉得是编译器为了字节对齐,调整了压栈顺序 |
2
abowloflrf OP @pagict 开 -O0 也是这样
|
3
across 2018-07-18 17:29:47 +08:00
不知道的。猜是在词法分析阶段排序过了,数组在后面内置类型后面吧。
|
4
abowloflrf OP @across 对从结果上看确实是这样,只是没找到解释
|
5
zhicheng 2018-07-18 17:46:59 +08:00 via iPhone 1
任何不符合标准的操作,行为都是不确定的,一万个编译器可以有一万种做法,没有必要去深究这个。
|
6
BlackKey 2018-07-18 17:50:41 +08:00
像变量在栈上如何排布和编译器以及环境有关,不属于 C 语言标准要求的内容,包括开了优化以后内存和变量都不一定是一一对应的关系了。
我印象中 C 唯一对内存排列有要求的部分是结构体内的变量必须按顺序排。 也许编译器的文档会有这方面规则的描述,总之这种规则都是编译时定的。 另外,如果你说 x86 架构的话,压栈的顺序是从高地址向低地址的,也就是说地址高的是先被压入栈的。 |
7
kljsandjb 2018-07-18 18:02:04 +08:00 via iPhone 1
我觉得吧,1. 好的习惯是把数组 a 定义在前面 2. 没出问题只是恰好没有缓冲区溢出,从安全角度来讲很容易被攻击,比如把 a 作为参数传递,数组退化成指针,修改内存,这个时候超过一定量,比如你这边是 10,就覆盖调用者的栈了,严重点覆盖返回值,然后你函数不知道返回到哪儿去了 23333
|
8
kljsandjb 2018-07-18 18:06:50 +08:00 via iPhone
关于顺序的话,我猜测是优化,不过这方面也不精通…坐等大神来解:)
|
9
easylee 2018-07-18 18:09:38 +08:00 via Android
请问楼主的编译器版本号是啥?
|
10
hx1997 2018-07-18 20:01:29 +08:00 2
试试加上 -fno-stack-protector 选项再编译看看,可能是 GCC 启用了 canary 栈保护。
|
11
hx1997 2018-07-18 20:05:22 +08:00
现代编译器默认会启用一些防止栈溢出攻击的技术,比如 canary 或者 NX,可以通过开关来关闭这些技术。
|
12
hx1997 2018-07-18 20:10:44 +08:00
@hx1997 #10 严格来说调整局部变量顺序不算 canary😂,不过 canary 和调整顺序都是 GCC stack-smashing protection 的一部分。
|
13
abowloflrf OP |
14
jsrdzhk 2018-07-18 20:21:14 +08:00 via Android
你编成汇编看看😁我瞎说的
|
15
BlackCat02 2018-07-18 20:44:37 +08:00 1
这种例子都是只存在于教科书上的例子了,实际编译器的实现中,变量的排布都是不一样的,数组溢出之后不一定就恰好覆盖 i。事实上 i 都不一定在栈上保存,可能直接被优化成了纯寄存器的变量
|
16
kljsandjb 2018-07-18 20:45:38 +08:00 via iPhone
@abowloflrf 加了 canary 后,发生栈破坏一般会终止程序,另外从内存利用效率来说的话,结构体中元素一般会建议按照从大到小顺序排列,也就是 6 楼提到的对齐的补充 :)
|
17
abowloflrf OP @kljsandjb 嗯是的,就是脑子里也默认把这些变量也当作和结构体一样顺序排列的了,今天遇到这个问题调试看地址才知道编译器会调整局部变量的位置而不是按照代码声明的顺序来的哈哈
|
18
kljsandjb 2018-07-18 21:09:48 +08:00 via iPhone
@abowloflrf 哈,我也是正好最近在学习到 csapp 第三章,现学现卖了 :)
|
19
reus 2018-07-19 10:44:31 +08:00
深究 UB 毫无意义。
|