在 g++ 中使用符号 '_end' 会导致分段错误
Using symbol '_end' in g++ leads to a segmentation fault
考虑以下 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_
,一切正常。
此外,如果我们通过提供-S命令行参数来要求gcc输出汇编代码,那么带有“_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 [=11=], _end(,%rax,4)
---
> movl [=11=], end_(,%rax,4)
但是如果我们使用 objdump 转储可执行文件并对它们进行 运行 比较,我们将看到在 _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 [=12=]x0,0x6020c8(,%rax,4)
---
> 4004ff: c7 04 85 60 10 60 00 movl [=12=]x0,0x601060(,%rax,4)
据我所知,gcc 编译器可能会根据需要处理以下划线开头的变量,i。 e.在您的代码中使用此类符号是一种不好的做法。但我的问题是:这里到底发生了什么?为什么 _end
被替换为已分配数组末尾的地址?为什么如果我们使用“-S”命令行参数没有区别,但创建的二进制文件实际上有区别?并不是说 gcc 和 clang 在这种情况下表现相同,这对我来说也很奇怪。
以 _
开头的标记是保留的,您不应使用它们。 _end
似乎是为在 Linux 上编译的程序定义的外部符号,表示未初始化数据段(也称为 BSS 段)末尾之后的第一个地址。
Note: On some systems the names of these symbols are preceded by
underscores, thus: _etext, _edata, and _end.
C99 N1256 standard draft 7.1.3 "Reserved identifiers" 说:
All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.
那么我们必须知道:
- 文件范围是全局变量(其他是函数和块范围)
- 普通名称space包括变量
因此根据 C99,您不能使用标识符 _end
。
你执行
现在要了解它在您的实施中实际失败的原因,请使用:
g++ -Wl,--verbose main.c
查看使用的链接描述文件。
在Ubuntu15.10,它定义了数据段末尾的符号_end
:
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
所以难怪提前访问内存可能会出现段错误。
考虑以下 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_
,一切正常。
此外,如果我们通过提供-S命令行参数来要求gcc输出汇编代码,那么带有“_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 [=11=], _end(,%rax,4)
---
> movl [=11=], end_(,%rax,4)
但是如果我们使用 objdump 转储可执行文件并对它们进行 运行 比较,我们将看到在 _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 [=12=]x0,0x6020c8(,%rax,4)
---
> 4004ff: c7 04 85 60 10 60 00 movl [=12=]x0,0x601060(,%rax,4)
据我所知,gcc 编译器可能会根据需要处理以下划线开头的变量,i。 e.在您的代码中使用此类符号是一种不好的做法。但我的问题是:这里到底发生了什么?为什么 _end
被替换为已分配数组末尾的地址?为什么如果我们使用“-S”命令行参数没有区别,但创建的二进制文件实际上有区别?并不是说 gcc 和 clang 在这种情况下表现相同,这对我来说也很奇怪。
以 _
开头的标记是保留的,您不应使用它们。 _end
似乎是为在 Linux 上编译的程序定义的外部符号,表示未初始化数据段(也称为 BSS 段)末尾之后的第一个地址。
Note: On some systems the names of these symbols are preceded by underscores, thus: _etext, _edata, and _end.
C99 N1256 standard draft 7.1.3 "Reserved identifiers" 说:
All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.
那么我们必须知道:
- 文件范围是全局变量(其他是函数和块范围)
- 普通名称space包括变量
因此根据 C99,您不能使用标识符 _end
。
你执行
现在要了解它在您的实施中实际失败的原因,请使用:
g++ -Wl,--verbose main.c
查看使用的链接描述文件。
在Ubuntu15.10,它定义了数据段末尾的符号_end
:
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
所以难怪提前访问内存可能会出现段错误。