考虑以下c ++源代码:
int _end[1050];
int main() {
for (int i = 0; i < 1050; i++)
_end[i] = 0;
return 0;
}
编译行:g++ main.cpp -o main -O0
在Ubuntu 14.04下使用gcc-4.8.4和clang-3.6.0时,运行此代码会导致分段错误。奇怪的行为是符号_end
指向静态分配的数组_end
的末尾,而不是它的开头。如果我们将_end
替换为end_
,那么一切正常。
此外,如果我们要求gcc通过提供-S命令行参数输出汇编代码,那么版本与&#34; _end&#34;之间没有显着差异。以及具有任何其他数组名称的版本:
$ g++ main.cpp -o main.s -O0 -S
$ g++ main2.cpp -o main2.s -O0 -S
$ diff main.s main2.s
1,2c1,2
< .file "main.cpp"
< .globl _end
---
> .file "main2.cpp"
> .globl end_
5,7c5,7
< .type _end, @object
< .size _end, 4200
< _end:
---
> .type end_, @object
> .size end_, 4200
> end_:
25c25
< movl $0, _end(,%rax,4)
---
> movl $0, end_(,%rax,4)
但是如果我们使用objdump转储可执行文件并对它们运行diff,我们将会看到_end
版本中使用的地址比所需的地址多4200 = 4 * 1050字节:
$ g++ main.cpp -o main -O0
$ g++ main2.cpp -o main2 -O0
$ objdump -d main >main.dump
$ objdump -d main2 > main2.dump
$ diff main.dump main2.dump
2c2
< main: формат файла elf64-x86-64 // "File format" in Russian
---
> main2: формат файла elf64-x86-64
123c123
< 4004ff: c7 04 85 c8 20 60 00 movl $0x0,0x6020c8(,%rax,4)
---
> 4004ff: c7 04 85 60 10 60 00 movl $0x0,0x601060(,%rax,4)
据我所知,gcc编译器可能会根据需要处理以下划线开头的变量,i。即在代码中使用这些符号是一种不好的做法。但我的问题是:这里到底发生了什么?为什么_end
被替换为已分配数组末尾的地址?如果我们使用&#34; -S&#34;为什么没有区别?命令行参数,但创建的二进制文件实际上有区别吗?在这种情况下,gcc和clang的表现并不相同,这对我来说也很奇怪。
答案 0 :(得分:2)
以_
开头的代币是保留的,您不应该使用它们。似乎_end
是为在Linux上编译的程序定义的外部符号,并且表示超过未初始化数据段(也称为BSS段)末尾的第一个地址。
注意:在某些系统上,这些符号的名称前面有 下划线,因此:_etext,_edata和_end。
答案 1 :(得分:0)
C99 N1256 standard draft 7.1.3“保留标识符”说:
所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。
然后我们必须知道:
因此,根据C99,您无法使用标识符_end
。
您实施
现在看看为什么它实际上无法实现,请使用:
g++ -Wl,--verbose main.c
查看使用的链接描述文件。
在Ubuntu 15.10上,它在数据部分的末尾定义了符号_end
:
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
所以毫无疑问,在它之前访问内存可能会出现段错误。