考虑以下 C++ 源代码:
编译命令行:
此外,如果我们要求gcc通过提供-S命令行参数输出汇编代码,则使用"_end"和任何其他数组名称版本之间没有明显区别:
据我所知,gcc编译器可能会任意处理以下划线开头的变量,也就是说,在代码中使用这种符号是一个不好的习惯。但我的问题是:这里到底发生了什么?为什么
int _end[1050];
int main() {
for (int i = 0; i < 1050; i++)
_end[i] = 0;
return 0;
}
编译命令行:
g++ main.cpp -o main -O0
在使用gcc-4.8.4和clang-3.6.0在Ubuntu 14.04下运行此代码时,会导致分段错误。奇怪的是符号_end
指向静态分配的数组_end
的末尾,而不是它的开头。如果我们用end_
代替_end
,一切都能正常工作。此外,如果我们要求gcc通过提供-S命令行参数输出汇编代码,则使用"_end"和任何其他数组名称版本之间没有明显区别:
$ 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编译器可能会任意处理以下划线开头的变量,也就是说,在代码中使用这种符号是一个不好的习惯。但我的问题是:这里到底发生了什么?为什么
_end
被替换为分配的数组末尾的地址?为什么使用"-S"命令行参数没有区别,但是创建的二进制文件实际上有所不同?另外,gcc和clang在这种情况下的行为是相同的,这也让我感到奇怪。
_end
是一个指针,就像其他指针一样,当你赋值给数组时会进行指针运算。 - vsoftco