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

pu3pd22g  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(232)

考虑下面的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命令行参数来输出汇编代码,则带有“_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在这种情况下的行为完全相同,这对我来说也很奇怪。

nwlqm0z1

nwlqm0z11#

_开始的标记是保留的,不应该使用它们。_end似乎是为Linux上编译的程序定义的外部符号,它代表未初始化数据段(也称为BSS段)末尾之后的第一个地址。

  • 注:在某些系统上,这些符号的名称前面带有下划线,因此:_etext、_edata和_end。*

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

kx5bkwkv

kx5bkwkv2#

**C99 N1256 standard draft**7.1.3“保留标识符”表示:

所有以下划线开始的标识符始终保留用作普通和标记名称空间中文件范围的标识符。
那么我们必须知道:

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

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

GCC实施

现在,要了解它在您的实现中实际失败的原因,请用途:

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

以查看使用的连接器指令码。
在Ubuntu 15.10上,它在数据段的末尾定义了符号_end

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

因此,在它之前访问内存可能会发生segfault也就不足为奇了。

相关问题