talk is cheap,来观摩一下简单代码 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
microxiaoxiao
V2EX    程序员

talk is cheap,来观摩一下简单代码

  •  
  •   microxiaoxiao 2022-05-27 09:35:02 +08:00 5385 次点击
    这是一个创建于 1243 天前的主题,其中的信息可能已经有所发展或是发生改变。
    void test(char *p, int len)
    {
    snprintf(out,len,"%s","hello world");
    }
    int main()
    {
    char res[512];
    {
    char result[1024];
    test(result,1024);
    }
    printf("result %s\n",res);
    }

    问题:在 ubuntu20.04 上面会输出 hello world 。
    其他环境;公司自研系统正常,suse12 正常
    问题,为啥 ubuntu 有这种骚操作->把 res 和 result 搞成同一个地址。
    手机打字不容易,不好排版,见谅。
    32 条回复    2022-05-28 16:12:39 +08:00
    eote
        1
    eote  
       2022-05-27 09:40:33 +08:00
    c 里面数组初始化都不分配默认值的吗?那里面有啥都不奇怪
    MoYi123
        2
    MoYi123  
       2022-05-27 09:46:11 +08:00
    有没有可能你需要 memset 一下 res.
    microxiaoxiao
        3
    microxiaoxiao  
    OP
       2022-05-27 09:50:04 +08:00
    @eote 不是说他默认值的问题,是 res 地址和 result 地址一致,导致在调用的时候被覆盖了。在 test 中写入的就是 res 的地址。
    microxiaoxiao
        4
    microxiaoxiao  
    OP
       2022-05-27 09:52:54 +08:00
    @eote memset 和直接初始化当然应该,我的主要问题是它把地址优化为一个地址是不是不太对,因为如果,printf 之前对他进行付初值已经晚了
    wuruorocks
        5
    wuruorocks  
       2022-05-27 09:58:42 +08:00
    第 3 行的 out 在哪里定义的
    villivateur
        6
    villivateur  
       2022-05-27 09:59:21 +08:00
    在你的 test 函数里面,snprintf 的 len 参数 是 1024 ,而 "hello world" 又那么短,你确定不会发生内存访问错误?
    shyrock
        7
    shyrock  
       2022-05-27 09:59:32 +08:00
    snprintf(out,len,"%s","hello world");

    这句里面的 out 是啥?
    microxiaoxiao
        8
    microxiaoxiao  
    OP
       2022-05-27 10:01:27 +08:00
    @wuruorocks 写错了,就是 result 手机打字麻烦,函数那个 p 写成 out
    microxiaoxiao
        9
    microxiaoxiao  
    OP
       2022-05-27 10:02:07 +08:00
    @shyrock 手机打字,out 写成 p
    microxiaoxiao
        10
    microxiaoxiao  
    OP
       2022-05-27 10:03:08 +08:00
    void test(char *p, int len)
    {
    snprintf(p,len,"%s","hello world");
    }修改一下
    eote
        11
    eote  
       2022-05-27 10:06:20 +08:00   2
    @microxiaoxiao

    gcc 根本没申请 res[512]


    --前略

    main:
    .LFB1:
    .cfi_startproc
    endbr64
    pushq %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    subq $1040, %rsp
    movq %fs:40, %rax
    movq %rax, -8(%rbp)
    xorl %eax, %eax
    leaq -1040(%rbp), %rax
    movl $1024, %esi
    movq %rax, %rdi
    call test
    leaq -1040(%rbp), %rax
    movq %rax, %rsi
    leaq .LC2(%rip), %rdi
    movl $0, %eax
    call printf@PLT
    movl $0, %eax
    movq -8(%rbp), %rdx
    xorq %fs:40, %rdx
    je .L4
    call __stack_chk_fail@PLT
    -- 后略
    chenxytw
        12
    chenxytw  
       2022-05-27 10:06:58 +08:00   2
    和操作系统无关,和编译器有关系。
    Ubuntu 20.04 默认是 gcc9, suse12 默认是 gcc7 。
    未初始化的变量是 UB 行为,编译器理论上怎么做都没关系。
    变量声明是没有先后顺序的。
    ksco
        13
    ksco  
       2022-05-27 10:08:46 +08:00
    初始化一下 res:char res[512] = {0};
    senninha
        14
    senninha  
       2022-05-27 10:09:23 +08:00
    编译器版本不一样导致的,看一下汇编代码吧
    另外,在不会输出 hello world 的版本上 -O3 优化一下,估计也是 hello world.
    lonestar
        15
    lonestar  
       2022-05-27 10:16:09 +08:00
    有意思。ubuntu 18/gcc 7.5 也是这样。即使 -O0 也还是这样。

    考虑到在进入下级 { } 前这些局部变量并未使用,那么这样的行为也没有危害。只要在进入内部 { } 前随便引用一下 res ,编译器就会为 res 真正分配空间了。
    dianqk
        16
    dianqk  
       2022-05-27 10:27:08 +08:00
    试了一下 clang 在 ubuntu 、macos 、arch 上都是正常的行为,而 gcc 在 -O0 -O1 下都会出错,-O2 -O3 是没问题的。
    gcc 为这两个变量分配同一个地址没什么问题,但是看起来 -O0 下因为什么原因没有释放这部分内存(难不成是 bug )
    dianqk
        17
    dianqk  
       2022-05-27 10:37:46 +08:00
    说错了,栈上的内存哪有什么释放的一说,如上所说,**当使用一个 char[] 时候应当进行初始化。如果没初始化就会进入未定义行为。**
    事实上由于未定义行为,clang 一样会坏掉。
    encro
        18
    encro  
       2022-05-27 10:42:28 +08:00
    你适合 rust ,哈哈
    luassuns
        19
    luassuns  
       2022-05-27 10:43:45 +08:00
    suse gcc 12.1/clang14 没有这个问题
    dianqk
        20
    dianqk  
       2022-05-27 10:44:35 +08:00
    有一个方式理解起来可能容易一些,把 res 和 result 当成一个普通的 int 变量。
    由于在 { result } 的逻辑前面没有 res 的 define 和 use ,同时 { result } 完成后,result 不是 live (后面没有逻辑在使用 result )的,为了优化性能 result 自然可以复用 res 的地址 /寄存器。
    但是 result 的内容在栈 /寄存器上没有释放(我是指把 result 用到的内存 /寄存器设置为默认值)。

    (感觉自己表述的很烂,不知道我的思考是不是对的,应该差不多了
    关键还是使用一个变量但没有初始化,那这个变量就可能指向一个脏的空间(别人用过的)。
    ksco
        21
    ksco  
       2022-05-27 10:45:24 +08:00   1
    @DianQK #17 不写 UB 的代码就好了,纠结 UB 属于费力不讨好。
    mingl0280
        22
    mingl0280  
       2022-05-27 10:58:40 +08:00
    使用未初始化的变量是未定义行为,无讨论价值。
    建议锁帖。
    codehz
        23
    codehz  
       2022-05-27 10:59:08 +08:00
    @DianQK 局部变量哪有什么“释放”的概念。。。
    dianqk
        24
    dianqk  
       2022-05-27 11:17:38 +08:00
    @codehz 我表述错了
    sudoy
        25
    sudoy  
       2022-05-27 11:20:27 +08:00
    没排版的代码看着好难受
    microxiaoxiao
        26
    microxiaoxiao  
    OP
       2022-05-27 11:24:06 +08:00 via Android
    大家都很有见地,我就不一一回复了,打字不方便。感谢大家。这个问题综合大家说的,我的初步结论是这样的:char res[],过程其实并不分配内存,只有在真正引用的过程才会分配内存,咋们平时说的初始化和 memset print 都是针对它的引用。行为其实是定义了的,有些编译器可能给它分配到的是为使用过的地址,有些分配到的是未使用过的地址,表现为未定义,这样就会有所谓的数据随机化。感谢给汇编的思路。
    smdbh
        27
    smdbh  
       2022-05-27 11:42:15 +08:00
    作用域问题把
    weiwenhao
        28
    weiwenhao  
       2022-05-27 12:02:06 +08:00
    测试了一下,确实是 26 楼主这样的结论。

    发现这里大佬这么多,刚好也有一个疑惑点,在所有变量都初始化的情况下
    ```
    int8_t a = 1
    {
    int8_t b = 2
    }
    int8_t c = 3
    ```

    类似这种情况,int8_t b 的作用域离开后不久没用了吗, int8_t b 和 int8_t c 完全可以使用同一段栈空间, 这样不是更节省空间吗, 但是实际上 gcc 编译器生成的代码是这样的, 为每个变量都分配了栈空间,完全没有考虑作用域? 这样做有啥好处吗
    movb $1, -3(%rbp)
    movb $2, -2(%rbp)
    movb $3, -1(%rbp)
    iamzuoxinyu
        29
    iamzuoxinyu  
       2022-05-27 13:01:23 +08:00
    @weiwenhao C 没有 RAII 。只有作用域访问限制。
    Caturra
        30
    Caturra  
       2022-05-27 13:51:49 +08:00
    虽然说不是一定要全部初始化,但你不初始化那么`res`的内容完全可以任由编译器去解释
    我丢到 godbolt 上看确实不同的 gcc 版本结果不一样,生成的指令各不相同就不细看了

    其中一种可以解释为:
    1. 因为你用`result`前(调用`test`)的代码并没有用到`res`
    2. gcc 打算把它放到`result`的前 512 字节中,既在`printf`前其实只有栈指针 rsp - 1024 而不是 1024+512
    3. 而`res`从起始地址开始就因为`test`被塞入了 hello world ,所以也会莫名其妙输出 hello world

    其实意思就是编译器把原来第 2 行的`res`定义优化了,放到原来第 4 行后面,栈指针可以省点偏移量

    要避免这种 UB ,又舍不得`memset`整个`res`的话,可以用`res[0] = 0`,直接在首部塞入结束符
    不过写这种代码我觉得还是别像资本家发工资那么抠门吧,直接 memset 就好了
    dianqk
        31
    dianqk  
       2022-05-27 14:06:12 +08:00
    @weiwenhao O0 编译?改成 O1 这三个变量就会用同一个寄存器了
    (不过我不知道怎么避免执行 DCE ,我加了个辅助函数

    extern void use_i(int8_t);

    int main() {
    int8_t a = 1;
    use_i(a);
    {
    int8_t b = 2;
    use_i(b);
    }
    int8_t c = 3;
    use_i(c);
    }
    secondwtq
        32
    secondwtq  
       2022-05-28 16:12:39 +08:00
    @weiwenhao 这问题没法回答,因为编译器优化是个 best effort 的过程,你不能保证他一定会给你优化
    不优化不一定是“有什么好处”,有可能只是触发了一个 bug 或者 edge case ,这是和具体实现相关的
    现在的大多数 C/C++ 程序员被灌输的认知一般是“编译器很聪明”(甚至 Javascript 等语言的程序员也出现了这种现象),但实际上编译器有时候也很笨。客观的来说,编译器优化只是一个暴力循环自动算法的过程而已。有时候灵,有时候不灵。

    但是只要程序的可见行为符合标准,原则上就没问题
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5343 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 08:13 PVG 16:13 LAX 01:13 JFK 04:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86