将 __attribute__((used)) 设置为 C 变量/常量没有效果

LuC*_*LuC 3 c gcc arm nxp-microcontroller

在 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
};
Run Code Online (Sandbox Code Playgroud)

没有在代码中引用它,它已被优化,并在地图文件上的废弃输入部分中列出。仅当我在源代码的其他地方引用它时,它才会包含在二进制输出中。

仅仅“ used”标签就足够了吗?在GCC手册(6.34.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
Run Code Online (Sandbox Code Playgroud)

Mik*_*han 10

仅仅“二手”标签就足够了吗?

这还不够,也没有必要。这是不相关的。

根据您引用的 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
};
Run Code Online (Sandbox Code Playgroud)

默认情况下,我们已将ApplicationID其定义为extern变量。文件范围变量的默认存储类(例如 )ApplicationIDextern。编译器将相应地生成一个目标文件,其中公开 的定义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
Run Code Online (Sandbox Code Playgroud)

在目标文件中,第 #4 节中ApplicationID是一个 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
};
Run Code Online (Sandbox Code Playgroud)

我们已经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
     ...
Run Code Online (Sandbox Code Playgroud)

在这个目标文件中,在节中ApplicationID是一个16字节的LOCAL符号绑定意味着静态链接器看不到这个符号。.rodataLOCAL

编译器将始终在目标文件中发出变量的定义extern,例如ApplicationIDin的app_id_extern.c定义,即使该定义未在目标文件中引用,因为外部定义可供链接器使用,因此可能会在以下位置引用:对于编译器可能知道的所有信息,来自其他目标文件的链接时间。

但如果变量是static,则编译器知道其定义不可用于链接。因此,如果它可以确定该定义没有在目标文件本身内被引用,则可以得出结论该定义是多余的并且根本不在目标文件中发出它。就像这样:

$ gcc -O1 -c app_id_static.c
Run Code Online (Sandbox Code Playgroud)

这次,我们要求编译器执行最小的优化。进而

$ 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
Run Code Online (Sandbox Code Playgroud)

未引用的定义不再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
};
Run Code Online (Sandbox Code Playgroud)

我们再次使用 -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
     ...
Run Code Online (Sandbox Code Playgroud)

但这一次,多亏了 attribute usedLOCAL的定义ApplicationID 重新出现在第 #4 节中(在该目标文件中是.rodata.$AppID

这就是属性的used工作原理。它影响编译器的行为:它对链接器没有影响。

我们还没有进行任何链接。现在让我们做一些吧。

/* hello_world.c */

#include <stdio.h>

int main(void)
{
    puts("Hello world!")
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

该程序没有引用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
Run Code Online (Sandbox Code Playgroud)

在链接中,我要求删除未使用的输入部分,并输出映射文件(-Wl,-gc-sections,-Map=mapfile.txt

在地图文件中我们发现:

地图文件.txt

...
Discarded input sections
  ...
  .rodata.$AppID
                0x0000000000000000       0x10 app_id_static_used.o
  ...
Run Code Online (Sandbox Code Playgroud)

链接器已丢弃.rodata.$AppID来自的节输入app_id_static_used.o ,因为程序中没有引用该节中定义的符号。使用 attribute ,我们迫使编译器在 中发出该符号used的定义。这并不强制链接器需要它,或将其保留在可执行文件中。staticapp_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
};
Run Code Online (Sandbox Code Playgroud)

然后我们正在做你所做的事情,将属性应用于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
};
Run Code Online (Sandbox Code Playgroud)

然后您需要做的是通知链接器您希望保留符号的定义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
Run Code Online (Sandbox Code Playgroud)

现在程序包含 的定义ApplicationID,尽管没有引用它:

$ readelf -s hello | grep ApplicationID
    58: 0000000000002010    16 OBJECT  GLOBAL DEFAULT   18 ApplicationID
Run Code Online (Sandbox Code Playgroud)

第 18 节是.rodata程序的部分:

$ readelf --sections hello | grep '.rodata'
  [18] .rodata           PROGBITS         0000000000002000  00002000
Run Code Online (Sandbox Code Playgroud)

最后,请注意,输入部分.rodata.$AppIDapp_id_extern_section.o 合并到输出部分中.rodata,因为链接器的默认链接器脚本指定:

.rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
Run Code Online (Sandbox Code Playgroud)

即所有匹配 或 的输入部分.rodata.rodata.*输出.gnu.linkonce.r.*.rodata。这意味着甚至:

__attribute__((section(".rodata.$AppID")))
Run Code Online (Sandbox Code Playgroud)

是多余的。因此,app-id 源文件也可能只是我一开始使用的文件,app_id_extern.c并且链接选项--undefined=ApplicationID 是在程序中保留未引用符号所需的全部内容。除非您的链接器在这方面有所不同,否则您会发现相同的。