防止gcc删除未使用的变量

27

在我们的源文件中通常有类似这样的版本字符串:

static const char srcvers[] = "VERSION/foo.c/1.01/09.04.15";

当该字符串未被优化时,在某些情况下它非常有用,因为可以通过简单地调用strings a.out | grep VERSION来确定每个源文件与可执行文件链接的版本。

不幸的是,gcc(使用“-O”选项)会对其进行优化。所以我的问题是,是否有一种简单的方法(编译器开关会很好),使gcc保留该变量(它的名称始终相同),而不关闭其他任何优化。

编辑

我认为这个问题与那个问题的不同之处在于,我希望找到一种解决方案,而无需触及数千个源文件。


你想在所有二进制文件中添加 -v 选项来显示这个变量吗? - Ôrel
也许可以尝试欺骗gcc,让它认为该变量已被使用(例如strlen(srcvers);)? - Drew McGowen
3
尝试将其声明为 volatile 变量?这样应该可以工作:volatile static const char srcvers[] = "VERSION/foo.c/1.01/09.04.15"; - Sam Protsenko
@Ôrel 你是指 a.out -v 吗?这只能适用于一个源文件,而我们已经这样做了。我想获取所有链接在一起的文件的信息。 - Ingo Leonhardt
你可以直接移除 static 这个关键字。因为它不是静态的,所以必须是 public 的,这样编译器才不会将其移除。可能有成千上万个文件,但你可以使用 sed 命令来处理它们。 - cup
显示剩余6条评论
5个回答

39

你可以使用__attribute__((used))这个gcc(在clang中也适用)特殊属性(我看到问题标记了gcc)来实现:

将此属性附加到函数上,表示必须为该函数发出代码,即使似乎没有引用该函数。例如,在仅在内联汇编中引用函数时,这非常有用。

源自https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

演示:

$ cat a.c
static const char srcvers[] __attribute__((used)) = "VERSION/foo.c/1.01/09.04.15";
$ gcc -O3 -c a.c
$ strings a.o
VERSION/foo.c/1.01/09.04.15
你可以使用一些 #if#define 来使其更加简洁,并且能够在不支持该扩展的编译器上编译。

你可以使用一些 #if#define 来使其更加简洁,并且能够在不支持该扩展的编译器上编译。


2
尽管文档摘录描述了此属性与函数的使用方式,但是相同的属性也可以与变量一起使用,只要它们具有静态存储(在这种情况下)。 - John Bollinger
@IngoLeonhardt - 你可以使用链接器脚本做类似的事情。 - Carl Norum
https://dev59.com/Pmkw5IYBdhLWcg3wirCs - Carl Norum
请注意,attribute((used))不能用于局部变量,并且gcc 6已经变得更加聪明,可以优化掉您似乎没有使用的局部变量。 - Daniel Santos
8
__attribute__((used))在我的全局变量中没有起作用(文档暗示它仅适用于函数)(arm-none-eabi gcc 7),但通过__attribute__((section(".data")))将该符号放置在不同的节中确实可以起作用。这可能是因为链接器只能在通过-fdata-sections给符号分配自己的段时才能剥离它们。虽然我不太喜欢这种方法,但它确实可以解决问题。 - szmoore
显示剩余5条评论

5
我理解你的问题是,你需要在不修改源代码的情况下为每个对象文件添加版本字符串。可以通过以下方式实现。
创建头文件,例如include/version.h
#ifndef VERSION_H
#define VERSION_H

static const char _ver[] __attribute__((used)) = "VERSION/foo.c/1.01/09.04.15";

#endif /* VERSION_H */

然后在你的Makefile(或任何其他构建系统)中添加下一个gcc标志:

CPPFLAGS += -include include/version.h

当然应该将其传递给gcc,例如像这样:
%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -o $(*).o -c $(*).c

现在您可以观察到您的_ver字符串编译到每个对象文件中:
$ objdump -DS src/main.o | grep _ver

这将会显示类似于这样的内容:

Disassembly of section .rodata._ver:
00000000 <_ver>:

版本字符串已经存在,但已经被优化掉了(当然我需要像“03.01.01”这样具有日期的版本字符串,因为一些文件很旧)。但是,使用“-include”选项的想法似乎可以解决这个问题。再次感谢! - Ingo Leonhardt
啊,我看到你的源文件有不同的版本。我以为那个版本是指整个项目呢。 - Sam Protsenko
不,那会太简单了(实际上,我们已经知道这个额外信息,但有时您只需要确定某个库包含的文件中的某些更改是否链接了) - Ingo Leonhardt
好的,那么最好的解决方法就是修改每个源文件中的代码(添加__attribute__((used)))。可以使用bash oneliner轻松完成,使用findsed命令。你有什么阻止你这样做的东西吗? - Sam Protsenko
我找到了一种实现你想要的方式,但它有点丑陋,你可能不会喜欢 :) 在编译之前,你可以生成新的源文件,并将__attribute__((used))添加到你的版本字符串中,然后使用生成的文件运行构建过程。在构建完成后,你可以立即删除这些生成的文件。 - Sam Protsenko
显示剩余2条评论

1

似乎所有的解决方案都需要在源代码中增加一些版本字符串的修饰,定义一个包含所有必要语法的宏可能有所帮助,然后在需要的时候在源文件或头文件中使用这个宏:

#define SRCVERSION(file, version, data) static const char _ver[] __attribute__((used)) = "VERSION/" file "/" version "/" date;

然后在你的源代码中只需放置:

SRCVERSION("foo.c", "1.01", "09.04.15")

宏可以位于中央项目头文件或编译器的命令行上。
这样,如果您想更改定义的某些内容,至少不必再次触及所有源文件。
请注意,宏定义使用字符串连接构建最终版本字符串。同时,它包含了最终的分号,因此如果需要,您可以通过定义空宏来删除所有内容。

1

将变量声明为volatile也可以有所帮助。这就是为什么它首先被使用的原因,防止编译器对该变量进行任何优化。


这确实有帮助;我已经检查过了。但是,这意味着要触及每个文件 :-(。无论如何,还是谢谢。 - Ingo Leonhardt
4
请注意,这是 实现定义。没有保证这将起作用,因为标准只要求 volatile 影响对变量的访问。如果变量从未使用,则编译器可自由删除它。 - user694733
是的,只是确认一下@user694733 - 刚刚尝试了在GCC C项目中使用volatile以防止未使用的变量被删除/优化,但它仍然被优化掉了。 - sdbbs

0
你关心的是gcc删除了一个未使用的静态字符数组变量。据我所知,编译器这样做是正确的。
其他答案提供了改进的建议。但是你不想改变成千上万个文件的源代码。
然后,你或许可以更改构建过程(例如一些Makefile),使得每个使用你的技巧的源文件(正如在此处讨论的那样,有点错误)都不需要被修改。所以你可能会特定地调用GCC。你希望
 static const char _ver[] __attribute__((used));

这是一个声明,而不是定义,需要在任何其他代码之前编译。将上述行放入某个_declare_ver.h文件中,并使用gcc -include _declare_ver.h命令进行编译(而不是使用gcc。如果使用make,请添加

 CFLAGS += -include _declare_ver.h

在你的Makefile中。

顺便说一句,那是一个不太好的技巧。你应该考虑做些更好的事情(遵循其他答案)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接