c - 将 __attribute__((used)) 设置为 C 变量/常量无效
问题描述
在 ARM GCC(纯 C 代码)上,当我声明一个常量时
__attribute__((used,section(".rodata.$AppID")))
const uint8_t ApplicationID[16] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
而且我没有在代码中引用它,它已经过优化,并列在地图文件的Discarded input 部分中。只有当我在源代码的其他地方引用它时,它才会包含在二进制输出中。
仅“ used
”标签还不够吗?在 GCC 手册(6.32.1 公共变量属性)中,我读到:
用过的
此属性附加到具有静态存储的变量,意味着即使看起来未引用该变量,也必须发出该变量。
意思是把它放在一个固定的内存地址,在指定的部分,供单独的应用程序检查它
我正在运行随 NXP MCUXpresso 11.1 提供的 ARM GCC,报告详细版本为
GNU C17 (GNU Tools for Arm Embedded Processors 8-2019-q3-update) version 8.3.1 20190703 (release) [gcc-8-branch revision 273027] (arm-none-eabi)
compiled by GNU C version 5.3.1 20160211, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl version isl-0.18-GMP
解决方案
仅“使用”标签不应该足够吗?
这还不够,也没有必要。这不相关。
根据您引用的 GCC 文档,属性used
适用于静态变量的定义。正如作者现在删除的答案所指出的那样,您ApplicationID
的不是静态的,因此属性used
无效。
这里:
/* app_id_extern.c */
#include <stdint.h>
const uint8_t ApplicationID[16] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
默认情况下,我们已将ApplicationID
其定义为extern
变量。文件作用域变量的默认存储类,如ApplicationID
,是extern
. 编译器将相应地生成一个目标文件,其中的定义ApplicationID
被公开以供链接,如我们所见:
$ gcc -c app_id_extern.c
$ readelf -s app_id_extern.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
...
9: 0000000000000000 16 OBJECT GLOBAL DEFAULT 4 ApplicationID
在目标文件中,ApplicationID
是第 #4 节中的 16 字节GLOBAL
符号(在这种情况下恰好是.rodata
)。GLOBAL
绑定意味着静态链接器可以看到这个符号。
和这里:
/* app_id_static.c */
#include <stdint.h>
static const uint8_t ApplicationID[16] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
我们已经ApplicationID
明确定义为一个static
变量。编译器将相应地生成一个目标文件,其中的定义ApplicationID
未公开用于链接,我们也可以看到:
$ gcc -c app_id_static.c
$ readelf -s app_id_static.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
...
6: 0000000000000000 16 OBJECT LOCAL DEFAULT 4 ApplicationID
...
在这个目标文件中,节ApplicationID
中有一个 16 字节的LOCAL
符号绑定意味着静态链接器看不到这个符号。.rodata
LOCAL
编译器将始终在目标文件中发出extern
变量的定义,例如ApplicationID
inapp_id_extern.c
的定义,即使该定义未在目标文件中引用,因为外部定义将对链接器可用,因此可能会在来自其他目标文件的链接时间,编译器可能知道的所有内容。
但是如果一个变量是static
,那么编译器就知道它的定义不可用于链接。因此,如果它可以确定该定义没有在目标文件本身中被引用,它可能会断定该定义是多余的并且根本不会在目标文件中发出它。像这样:
$ gcc -O1 -c app_id_static.c
这一次,我们要求编译器执行最小的优化。接着
$ readelf -s app_id_static.o
Symbol table '.symtab' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS app_id_static.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 6
7: 0000000000000000 0 SECTION LOCAL DEFAULT 4
对象文件中ApplicationID
不再存在
未引用的定义。它被优化了。
现在,对于某些不寻常的应用程序,我们可能希望编译器在不引用它的目标文件中发出符号的定义,并将其隐藏在静态链接器中。这就是属性used
发挥作用的地方:
/* app_id_static_used .c */
#include <stdint.h>
static const uint8_t ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
我们再次使用 -O1 优化进行编译:
$ gcc -O1 -c app_id_static_used.c
$ readelf -s app_id_static_used.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
...
6: 0000000000000000 16 OBJECT LOCAL DEFAULT 4 ApplicationID
...
但是这一次,多亏了 attribute used
,第 #4 节的LOCAL
定义ApplicationID
重新出现(在这个目标文件中是.rodata.$AppID
)
这就是属性的used
工作方式。它影响编译器的行为:它对链接器没有影响。
我们还没有进行任何关联。现在让我们做一些。
/* hello_world.c */
#include <stdio.h>
int main(void)
{
puts("Hello world!")
return 0;
}
该程序没有引用ApplicationID
,但无论如何我们都会输入app_id_static_used.o
链接:
$ gcc -O1 -c hello_world.c
$ gcc -o hello hello_world.o app_id_static_used.o -Wl,-gc-sections,-Map=mapfile.txt
在链接中,我要求删除未使用的输入部分,并要求输出映射文件 ( -Wl,-gc-sections,-Map=mapfile.txt
)
在地图文件中,我们发现:
地图文件.txt
...
Discarded input sections
...
.rodata.$AppID
0x0000000000000000 0x10 app_id_static_used.o
...
链接器已丢弃节.rodata.$AppID
输入,app_id_static_used.o
因为程序中没有引用该节中定义的符号。使用属性used
,我们强制编译器static
在app_id_static_used.o
. 这不会迫使链接器需要它,或者将它保存在可执行文件中。
在我们切换app_id_static_used.c
到:
/* app_id_extern_used.c */
#include <stdint.h>
const uint8_t ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
然后我们正在做你所做used
的,将属性应用于extern
定义。在这种情况下,属性
used
不起作用,因为编译器无论如何都会发出extern
定义。如果程序没有引用其中的任何内容,链接器仍会从可执行文件中丢弃输入部分。.rodata.$AppID
到目前为止,您的 app-id 源文件还可能是:
/* app_id_extern_section.c */
#include <stdint.h>
const uint8_t ApplicationID[16] __attribute__((section(".rodata.$AppID"))) = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
然后您需要做的是通知链接器您希望ApplicationID
保留符号的定义,即使它没有被您的程序引用,并且即使删除了未使用的部分。
为此,请使用链接器选项--undefined=ApplicationID
。这将指示链接器从一开始就假定您的程序的链接遇到了未定义的引用,ApplicationID
并强制链接器查找并链接其定义(如果有任何输入文件提供)。因此:
$ gcc -O1 -c app_id_extern_section.c
$ gcc -o hello hello_world.o app_id_extern_section.o -Wl,-gc-sections,--undefined=ApplicationID
现在程序包含 的定义ApplicationID
,尽管没有提到它:
$ readelf -s hello | grep ApplicationID
58: 0000000000002010 16 OBJECT GLOBAL DEFAULT 18 ApplicationID
第 #18.rodata
节是程序的部分:
$ readelf --sections hello | grep '.rodata'
[18] .rodata PROGBITS 0000000000002000 00002000
最后,请注意 input section .rodata.$AppID
fromapp_id_extern_section.o
已合并到 output section .rodata
,因为链接器的默认链接器脚本指定:
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
即所有匹配的输入部分.rodata
,.rodata.*
或.gnu.linkonce.r.*
将输出到.rodata
。这意味着即使:
__attribute__((section(".rodata.$AppID")))
是多余的。因此,app-id 源文件也可能只是我开始使用app_id_extern.c
的那个,链接选项--undefined=ApplicationID
是在程序中保留未引用符号所必需的。除非您的链接器在这方面有所不同,否则您会发现相同的。
推荐阅读
- assembly - 单精度浮点数的 MIPS 除法错误
- ios - 二进制使用不安全的禁用 api 和函数
- javascript - 请注意,'frame-src' 没有明确设置,所以 'default-src' 用作后备
- reactjs - React JS 从普通文本中渲染有趣和奇怪的字体
- windows-10 - 如何在 Windows 10 上找到挂起的 ssh 反向隧道
- reactjs - 如何从嵌套屏幕导航到特定导航器
- c++ - 如何修复我的 dll 中未处理的异常?
- python - 如何在没有循环的情况下在 NumPy 中实现“A 中的行 * B 中的所有行由 A 中的 col”乘法?
- apache-spark - 火花给予要求失败:文字必须具有与字符串对应的值,但找到了类字符串
- .net - 有人可以根据提供的信息协助了解 .Net Runtime Exception Info: System.TypeLoadException 的原因吗?