将 __attribute__((used)) 设置为 C variable/constant 无效
Setting __attribute__((used)) to C variable/constant has no effect
在 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
};
和我没有在代码中引用它,它优化掉,并列在丢弃在地图文件中输入 sections。
只有当我在源代码的其他地方引用它时,它才会包含在二进制输出中。
单靠“used
”标签不就足够了吗?在 GCC 手册 (6.34.1 Common Variable Attributes) 中,我读到:
used
This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.
的意思是放在固定的内存地址,在指定的section中,让单独的应用程序去检查
我是 运行 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
Shouldn't the "used" tag alone be enough?
不够,也没有必要。不相关。
根据您引用的 GCC 文档,属性 used
是
适用于 static 变量的定义。作为现在的答案
作者指出删除,你的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
变量。默认存储 class
一个文件范围变量,如 ApplicationID
,是 extern
。编译器将
相应地生成一个目标文件,其中定义了 ApplicationID
暴露了 linkage,正如我们所见:
$ 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
绑定意味着静态linker可以看到这个符号。
这里:
/* 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
的定义
未暴露link年龄,我们还可以看到:
$ 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
是 .rodata
部分中的 16 字节 LOCAL
符号
LOCAL
绑定意味着静态linker 不能看到这个符号。
编译器将总是在目标文件中发出一个extern
变量的定义,
就像 app_id_extern.c
中的 ApplicationID
,即使那个定义不是
在目标文件中引用,因为外部定义将可用于
linker,因此可能会在 link 时从其他目标文件中引用,因为
编译器可能知道。
但是如果一个变量是static
,那么编译器就知道它的定义是不可用的
link年龄。因此,如果它可以确定该定义未在
目标文件本身,它可能会断定该定义是多余的并且不会发出它
在目标文件中。像这样:
$ 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
的未引用定义
完全。它被优化了。
现在对于某些不寻常的应用程序,我们可能希望编译器发出定义
对象文件中未引用它的符号, 和 将其隐藏在静态 linker 之外。那是
属性 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
...
但这一次,由于属性 used
,ApplicationID
的 LOCAL
定义
再次出现在第 4 部分(在此目标文件中为 .rodata.$AppID
)
这就是属性 used
的工作原理。它影响编译器的行为:它没有
对 linker.
的影响
我们还没有完成任何 link年龄。让我们现在做一些。
/* hello_world.c */
#include <stdio.h>
int main(void)
{
puts("Hello world!")
return 0;
}
此程序没有引用 ApplicationID
,但我们将输入 app_id_static_used.o
到 link 年龄:
$ gcc -O1 -c hello_world.c
$ gcc -o hello hello_world.o app_id_static_used.o -Wl,-gc-sections,-Map=mapfile.txt
在 link 时代,我要求删除未使用的输入部分,并要求一个映射文件
待输出 (-Wl,-gc-sections,-Map=mapfile.txt
)
在映射文件中我们发现:
Mapfile.txt
...
Discarded input sections
...
.rodata.$AppID
0x0000000000000000 0x10 app_id_static_used.o
...
linker 丢弃了来自 app_id_static_used.o
的 .rodata.$AppID
部分
因为程序中没有引用该部分中定义的任何符号。和
属性 used
,我们强制编译器发出 static
符号的定义
在 app_id_static_used.o
。这不会迫使 linker 需要它,或保持
它在可执行文件中。
我们从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
任何情况下的定义。 linker 将 still 丢弃 .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
};
然后您需要做的是通知linker您想要定义
符号 ApplicationID
保留,即使它没有被您的程序引用,并且
如果即使未使用的部分被删除。
要实现这一点,请使用 linker 选项 --undefined=ApplicationID
。这会
指导 linker 从一开始就假设你的程序 linkage
遇到了对 ApplicationID
的未定义引用并迫使 linker
查找并 link 它的定义,如果有任何输入文件提供的话。因此:
$ 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
最后,请注意 app_id_extern_section.o
中的输入部分 .rodata.$AppID
已经合并到输出部分.rodata
,因为linker的默认
linker 脚本指定:
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
即所有匹配 .rodata
、.rodata.*
或 .gnu.linkonce.r.*
的输入部分
将输出到.rodata
。这意味着甚至:
__attribute__((section(".rodata.$AppID")))
是多余的。所以 app-id 源文件也可能就是那个
我从 app_id_extern.c
和 linkage 选项开始 --undefined=ApplicationID
是在程序中保留未引用符号所必需的。除非
你的 linker 在这方面是不同的,你会发现相同的。
在 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
};
和我没有在代码中引用它,它优化掉,并列在丢弃在地图文件中输入 sections。 只有当我在源代码的其他地方引用它时,它才会包含在二进制输出中。
单靠“used
”标签不就足够了吗?在 GCC 手册 (6.34.1 Common Variable Attributes) 中,我读到:
used
This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.
的意思是放在固定的内存地址,在指定的section中,让单独的应用程序去检查
我是 运行 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
Shouldn't the "used" tag alone be enough?
不够,也没有必要。不相关。
根据您引用的 GCC 文档,属性 used
是
适用于 static 变量的定义。作为现在的答案
作者指出删除,你的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
变量。默认存储 class
一个文件范围变量,如 ApplicationID
,是 extern
。编译器将
相应地生成一个目标文件,其中定义了 ApplicationID
暴露了 linkage,正如我们所见:
$ 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
绑定意味着静态linker可以看到这个符号。
这里:
/* 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
的定义
未暴露link年龄,我们还可以看到:
$ 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
是 .rodata
部分中的 16 字节 LOCAL
符号
LOCAL
绑定意味着静态linker 不能看到这个符号。
编译器将总是在目标文件中发出一个extern
变量的定义,
就像 app_id_extern.c
中的 ApplicationID
,即使那个定义不是
在目标文件中引用,因为外部定义将可用于
linker,因此可能会在 link 时从其他目标文件中引用,因为
编译器可能知道。
但是如果一个变量是static
,那么编译器就知道它的定义是不可用的
link年龄。因此,如果它可以确定该定义未在
目标文件本身,它可能会断定该定义是多余的并且不会发出它
在目标文件中。像这样:
$ 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
的未引用定义
完全。它被优化了。
现在对于某些不寻常的应用程序,我们可能希望编译器发出定义
对象文件中未引用它的符号, 和 将其隐藏在静态 linker 之外。那是
属性 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
...
但这一次,由于属性 used
,ApplicationID
的 LOCAL
定义
再次出现在第 4 部分(在此目标文件中为 .rodata.$AppID
)
这就是属性 used
的工作原理。它影响编译器的行为:它没有
对 linker.
我们还没有完成任何 link年龄。让我们现在做一些。
/* hello_world.c */
#include <stdio.h>
int main(void)
{
puts("Hello world!")
return 0;
}
此程序没有引用 ApplicationID
,但我们将输入 app_id_static_used.o
到 link 年龄:
$ gcc -O1 -c hello_world.c
$ gcc -o hello hello_world.o app_id_static_used.o -Wl,-gc-sections,-Map=mapfile.txt
在 link 时代,我要求删除未使用的输入部分,并要求一个映射文件
待输出 (-Wl,-gc-sections,-Map=mapfile.txt
)
在映射文件中我们发现:
Mapfile.txt
...
Discarded input sections
...
.rodata.$AppID
0x0000000000000000 0x10 app_id_static_used.o
...
linker 丢弃了来自 app_id_static_used.o
的 .rodata.$AppID
部分
因为程序中没有引用该部分中定义的任何符号。和
属性 used
,我们强制编译器发出 static
符号的定义
在 app_id_static_used.o
。这不会迫使 linker 需要它,或保持
它在可执行文件中。
我们从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
任何情况下的定义。 linker 将 still 丢弃 .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
};
然后您需要做的是通知linker您想要定义
符号 ApplicationID
保留,即使它没有被您的程序引用,并且
如果即使未使用的部分被删除。
要实现这一点,请使用 linker 选项 --undefined=ApplicationID
。这会
指导 linker 从一开始就假设你的程序 linkage
遇到了对 ApplicationID
的未定义引用并迫使 linker
查找并 link 它的定义,如果有任何输入文件提供的话。因此:
$ 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
最后,请注意 app_id_extern_section.o
中的输入部分 .rodata.$AppID
已经合并到输出部分.rodata
,因为linker的默认
linker 脚本指定:
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
即所有匹配 .rodata
、.rodata.*
或 .gnu.linkonce.r.*
的输入部分
将输出到.rodata
。这意味着甚至:
__attribute__((section(".rodata.$AppID")))
是多余的。所以 app-id 源文件也可能就是那个
我从 app_id_extern.c
和 linkage 选项开始 --undefined=ApplicationID
是在程序中保留未引用符号所必需的。除非
你的 linker 在这方面是不同的,你会发现相同的。