首页 > 解决方案 > 将 __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

标签: cgccarmnxp-microcontroller

解决方案


仅“使用”标签不应该足够吗?

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

根据您引用的 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符号绑定意味着静态链接器不到这个符号。.rodataLOCAL

编译器将始终在目标文件中发出extern变量的定义,例如ApplicationIDinapp_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,我们强制编译器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
};

然后我们正在做你所做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.$AppIDfromapp_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 是在程序中保留未引用符号所必需的。除非您的链接器在这方面有所不同,否则您会发现相同的。


推荐阅读