1
eote 2022-05-27 09:40:33 +08:00
c 里面数组初始化都不分配默认值的吗?那里面有啥都不奇怪
|
2
MoYi123 2022-05-27 09:46:11 +08:00
有没有可能你需要 memset 一下 res.
|
3
microxiaoxiao OP @eote 不是说他默认值的问题,是 res 地址和 result 地址一致,导致在调用的时候被覆盖了。在 test 中写入的就是 res 的地址。
|
4
microxiaoxiao OP @eote memset 和直接初始化当然应该,我的主要问题是它把地址优化为一个地址是不是不太对,因为如果,printf 之前对他进行付初值已经晚了
|
5
wuruorocks 2022-05-27 09:58:42 +08:00
第 3 行的 out 在哪里定义的
|
6
villivateur 2022-05-27 09:59:21 +08:00
在你的 test 函数里面,snprintf 的 len 参数 是 1024 ,而 "hello world" 又那么短,你确定不会发生内存访问错误?
|
7
shyrock 2022-05-27 09:59:32 +08:00
snprintf(out,len,"%s","hello world");
这句里面的 out 是啥? |
8
microxiaoxiao OP @wuruorocks 写错了,就是 result 手机打字麻烦,函数那个 p 写成 out
|
9
microxiaoxiao OP @shyrock 手机打字,out 写成 p
|
10
microxiaoxiao OP void test(char *p, int len)
{ snprintf(p,len,"%s","hello world"); }修改一下 |
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 -- 后略 |
12
chenxytw 2022-05-27 10:06:58 +08:00 2
和操作系统无关,和编译器有关系。
Ubuntu 20.04 默认是 gcc9, suse12 默认是 gcc7 。 未初始化的变量是 UB 行为,编译器理论上怎么做都没关系。 变量声明是没有先后顺序的。 |
13
ksco 2022-05-27 10:08:46 +08:00
初始化一下 res:char res[512] = {0};
|
14
senninha 2022-05-27 10:09:23 +08:00
编译器版本不一样导致的,看一下汇编代码吧
另外,在不会输出 hello world 的版本上 -O3 优化一下,估计也是 hello world. |
15
lonestar 2022-05-27 10:16:09 +08:00
有意思。ubuntu 18/gcc 7.5 也是这样。即使 -O0 也还是这样。
考虑到在进入下级 { } 前这些局部变量并未使用,那么这样的行为也没有危害。只要在进入内部 { } 前随便引用一下 res ,编译器就会为 res 真正分配空间了。 |
16
DianQK 2022-05-27 10:27:08 +08:00
试了一下 clang 在 ubuntu 、macos 、arch 上都是正常的行为,而 gcc 在 -O0 -O1 下都会出错,-O2 -O3 是没问题的。
gcc 为这两个变量分配同一个地址没什么问题,但是看起来 -O0 下因为什么原因没有释放这部分内存(难不成是 bug ) |
17
DianQK 2022-05-27 10:37:46 +08:00
说错了,栈上的内存哪有什么释放的一说,如上所说,**当使用一个 char[] 时候应当进行初始化。如果没初始化就会进入未定义行为。**
事实上由于未定义行为,clang 一样会坏掉。 |
18
encro 2022-05-27 10:42:28 +08:00
你适合 rust ,哈哈
|
19
luassuns 2022-05-27 10:43:45 +08:00
suse gcc 12.1/clang14 没有这个问题
|
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 用到的内存 /寄存器设置为默认值)。 (感觉自己表述的很烂,不知道我的思考是不是对的,应该差不多了 关键还是使用一个变量但没有初始化,那这个变量就可能指向一个脏的空间(别人用过的)。 |
22
mingl0280 2022-05-27 10:58:40 +08:00
使用未初始化的变量是未定义行为,无讨论价值。
建议锁帖。 |
25
sudoy 2022-05-27 11:20:27 +08:00
没排版的代码看着好难受
|
26
microxiaoxiao OP 大家都很有见地,我就不一一回复了,打字不方便。感谢大家。这个问题综合大家说的,我的初步结论是这样的:char res[],过程其实并不分配内存,只有在真正引用的过程才会分配内存,咋们平时说的初始化和 memset print 都是针对它的引用。行为其实是定义了的,有些编译器可能给它分配到的是为使用过的地址,有些分配到的是未使用过的地址,表现为未定义,这样就会有所谓的数据随机化。感谢给汇编的思路。
|
27
smdbh 2022-05-27 11:42:15 +08:00
作用域问题把
|
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) |
29
iamzuoxinyu 2022-05-27 13:01:23 +08:00
@weiwenhao C 没有 RAII 。只有作用域访问限制。
|
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 就好了 |