在g++中使用符号'_end'会导致分段错误

9
考虑以下 C++ 源代码:
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在这种情况下的行为是相同的,这也让我感到奇怪。
2个回答

3
_开头的标记是保留的,不应该使用它们。似乎_end是在Linux上编译程序时定义的外部符号,表示未初始化数据段(也称为BSS段)的结束地址。

注意:在某些系统上,这些符号的名称前面会有下划线,例如:_etext、_edata和_end。

来源:http://man7.org/linux/man-pages/man3/end.3.html

正是我所需要的,谢谢!但是为什么使用“-S”命令行参数编译这段代码时没有显示任何可疑信息呢? - Maxim Akhmedov
@MaximAkhmedov 这可能是因为_end是一个指针,就像其他指针一样,当你赋值给数组时会进行指针运算。 - vsoftco

1

C99 N1256标准草案 7.1.3 "保留标识符"说:

所有以下划线开头的标识符都始终保留用作普通名称空间和标记名称空间中具有文件作用域的标识符。

那么我们必须知道:

  • 文件作用域是全局变量(其他作用域包括函数和块级作用域)
  • 普通名称空间包括变量

因此根据C99,您不能使用标识符_end

GCC实现

现在要查看为什么它在您的实现上实际失败,请使用:

g++ -Wl,--verbose main.c

请查看使用的链接脚本。

在Ubuntu 15.10上,它在数据段末尾定义了符号_end

_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);

因此,访问比内存更前面的内容可能会导致段错误,这并不奇怪。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接