V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
abowloflrf
V2EX  ›  C

C 中变量在堆栈上分配内存顺序的一点疑问

  •  
  •   abowloflrf · 2018-07-18 17:14:21 +08:00 · 3692 次点击
    这是一个创建于 2311 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天在读 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 语言中变量内存分配的顺序是按照代码的顺序来的。但是它确实是有这样一个规律,我也好像没有找到什么官方的解释变量究竟是按照怎么样的顺序在堆栈上分配的,以及为何要这样做。

    在这里请教一下,不知道有没有人了解。

    第 1 条附言  ·  2018-07-18 20:21:29 +08:00

    谢谢 7 楼的提醒还有缓冲区溢出,没想到这里来,加上 -fno-stack-protector 关闭 GCC 的栈溢出保护编译内存地址就顺序分配了。至于具体内存分配顺序是怎么优化的 GCC 文档上应该有写,有兴趣的可以去找找看,我也不继续详细了解了。

    19 条回复    2018-07-19 10:44:31 +08:00
    pagict
        1
    pagict  
       2018-07-18 17:21:10 +08:00
    你把优化关了再试试呢

    觉得是编译器为了字节对齐,调整了压栈顺序
    abowloflrf
        2
    abowloflrf  
    OP
       2018-07-18 17:22:47 +08:00
    @pagict 开 -O0 也是这样
    across
        3
    across  
       2018-07-18 17:29:47 +08:00
    不知道的。猜是在词法分析阶段排序过了,数组在后面内置类型后面吧。
    abowloflrf
        4
    abowloflrf  
    OP
       2018-07-18 17:38:23 +08:00 via iPhone
    @across 对从结果上看确实是这样,只是没找到解释
    zhicheng
        5
    zhicheng  
       2018-07-18 17:46:59 +08:00 via iPhone   ❤️ 1
    任何不符合标准的操作,行为都是不确定的,一万个编译器可以有一万种做法,没有必要去深究这个。
    BlackKey
        6
    BlackKey  
       2018-07-18 17:50:41 +08:00
    像变量在栈上如何排布和编译器以及环境有关,不属于 C 语言标准要求的内容,包括开了优化以后内存和变量都不一定是一一对应的关系了。
    我印象中 C 唯一对内存排列有要求的部分是结构体内的变量必须按顺序排。
    也许编译器的文档会有这方面规则的描述,总之这种规则都是编译时定的。
    另外,如果你说 x86 架构的话,压栈的顺序是从高地址向低地址的,也就是说地址高的是先被压入栈的。
    kljsandjb
        7
    kljsandjb  
       2018-07-18 18:02:04 +08:00 via iPhone   ❤️ 1
    我觉得吧,1. 好的习惯是把数组 a 定义在前面 2. 没出问题只是恰好没有缓冲区溢出,从安全角度来讲很容易被攻击,比如把 a 作为参数传递,数组退化成指针,修改内存,这个时候超过一定量,比如你这边是 10,就覆盖调用者的栈了,严重点覆盖返回值,然后你函数不知道返回到哪儿去了 23333
    kljsandjb
        8
    kljsandjb  
       2018-07-18 18:06:50 +08:00 via iPhone
    关于顺序的话,我猜测是优化,不过这方面也不精通…坐等大神来解:)
    easylee
        9
    easylee  
       2018-07-18 18:09:38 +08:00 via Android
    请问楼主的编译器版本号是啥?
    hx1997
        10
    hx1997  
       2018-07-18 20:01:29 +08:00   ❤️ 2
    试试加上 -fno-stack-protector 选项再编译看看,可能是 GCC 启用了 canary 栈保护。
    hx1997
        11
    hx1997  
       2018-07-18 20:05:22 +08:00
    现代编译器默认会启用一些防止栈溢出攻击的技术,比如 canary 或者 NX,可以通过开关来关闭这些技术。
    hx1997
        12
    hx1997  
       2018-07-18 20:10:44 +08:00
    @hx1997 #10 严格来说调整局部变量顺序不算 canary😂,不过 canary 和调整顺序都是 GCC stack-smashing protection 的一部分。
    abowloflrf
        13
    abowloflrf  
    OP
       2018-07-18 20:17:26 +08:00
    @kljsandjb
    @hx1997

    是这样的,谢谢 7 楼的提醒还有缓冲区溢出,没想到这里来,刚刚下班路上查了下确实 gcc 有栈溢出保护的措施,是默认开启的,刚刚尝试加上-fno-stack-protector 编译然后地址也确实是顺序分配了。至于具体内存分配顺序是怎么优化的可能 GCC 文档上有写我也不详细将了解了。
    jsrdzhk
        14
    jsrdzhk  
       2018-07-18 20:21:14 +08:00 via Android
    你编成汇编看看😁我瞎说的
    BlackCat02
        15
    BlackCat02  
       2018-07-18 20:44:37 +08:00   ❤️ 1
    这种例子都是只存在于教科书上的例子了,实际编译器的实现中,变量的排布都是不一样的,数组溢出之后不一定就恰好覆盖 i。事实上 i 都不一定在栈上保存,可能直接被优化成了纯寄存器的变量
    kljsandjb
        16
    kljsandjb  
       2018-07-18 20:45:38 +08:00 via iPhone
    @abowloflrf 加了 canary 后,发生栈破坏一般会终止程序,另外从内存利用效率来说的话,结构体中元素一般会建议按照从大到小顺序排列,也就是 6 楼提到的对齐的补充 :)
    abowloflrf
        17
    abowloflrf  
    OP
       2018-07-18 21:00:52 +08:00
    @kljsandjb 嗯是的,就是脑子里也默认把这些变量也当作和结构体一样顺序排列的了,今天遇到这个问题调试看地址才知道编译器会调整局部变量的位置而不是按照代码声明的顺序来的哈哈
    kljsandjb
        18
    kljsandjb  
       2018-07-18 21:09:48 +08:00 via iPhone
    @abowloflrf 哈,我也是正好最近在学习到 csapp 第三章,现学现卖了 :)
    reus
        19
    reus  
       2018-07-19 10:44:31 +08:00
    深究 UB 毫无意义。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1334 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 17:49 · PVG 01:49 · LAX 09:49 · JFK 12:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.