未使用的C++未优化的静态成员函数/变量

Unused C++ static member functions/variables not optimized out

本文关键字:函数 变量 静态成员 优化 C++ 未使用      更新时间:2023-10-16

我启用了编译器和链接器优化,希望从我的ARM32可执行文件中删除所有未使用的代码/数据。从我的映射文件中,我可以看到未使用的代码部分确实被丢弃了,因此优化标志大部分都有效,除了未使用类的静态成员函数/变量。任何关于如何摆脱这种情况的想法将不胜感激,因为这在资源受限的嵌入式平台上加起来相当多!

这是在 Ubuntu 18.04 上使用 g++ 7.5 编译的 MVCE

#include <string>
#include <iostream>
#include <string.h>
class unusedClass {
public:
unusedClass() {};
~unusedClass() {};
static std::string className;
void initArray(void) {
memset(a, 0, sizeof(a));
}
void printArray(void) {
for (auto& i:a) {
std::cout<< i << std::endl;
}
}
static void printClassName(void) {
std::cout << "This is a static member function of the class "UNUSED CLASS""<< std::endl;
}
private:
int a[1000];
};
std::string unusedClass::className = "unusedClass";
int main() {
std::cout << "Running the Clean-up Dead Code Test" << std::endl;
return 0;
}

使用优化标志进行编译以删除未使用的代码

g++ -Os -flto test.cpp

检查静态成员变量是否已编译到可执行文件中

readelf -a --wide a.out | awk '$4 == "OBJECT" { print }'
29: 0000000000201140    32 OBJECT  LOCAL  DEFAULT   24 _ZN11unusedClass9classNameB5cxx11E

作为 StoryTeller 指针,_ZN11unusedClass9classNameB5cxx11E不是成员函数,而是成员变量static std::string className

static成员变量不能仅仅被优化掉,因为它们可以在多个翻译单元中访问。必须编译它们,以便链接器知道何时在不同位置使用相同的静态变量。

使用constexpr

如果不希望函数和变量编译到可执行文件中,则使用constexpr instead of static通常会导致此结果。 在您的示例中,类名在编译时是已知的,因此无论如何使用constexpr将是更惯用的解决方案。

constexpr意味着函数的inline,并暗示变量的"内在性"。由于constexpr变量的值在编译时都是已知的,因此无需将它们存储在编译的二进制文件中。这就是为什么编译器如果不使用,即使在-O0上也会优化它们。 下面是一个工作示例,其中未编译变量:https://godbolt.org/z/bqsuTA

注意:您只能在 constexpr 上下文中使用const char *,而不能在std::string中使用。

编辑:如果您改用static const char*,它也不会被优化出来。只有constexpr才能获得预期的结果:https://godbolt.org/z/DYNk2G

使用匿名namespace

请参阅以下示例:

namespace {
struct unused1 {;
static const int x;
};
}
struct unused2 {;
static const int x;
};
const int unused1::x = 1;
const int unused2::x = 2;

在这两个变量中,.long 2将始终在二进制文件中找到,即使在-O3上也是如此。

unused2::x:
.long   2

将类放入匿名命名空间类似于对函数使用static修饰符,并且不需要编译这些未使用的常量。

警告:如果将此类放入匿名命名空间,则不同的翻译单元将不再使用相同的static变量,而是使用自己的副本!

这真的很奇怪!我简化了您的代码示例并添加了一些变量和函数,以查看观察结果通常是否有效。

UC.H:

#include <string>
extern std::string mymystring1;
extern std::string mymystring2;
extern int mymyx1;
extern int mymyx2;
int mymyf1();
int mymyf

UC.cpp:

#include <string>
std::string mymystring1 ="This is a very simple test what happens if fdata sections did not work!";
std::string mymystring2 ="This variable should be used";
int mymyx1 = 11111; // unused
int mymyx2 = 22222;
int mymyf1() { return 1; } // unused
int mymyf2() { return 2; }

主.cpp:

#include "uc.h"
int main() {
std::cout << mymystring1 << std::endl;
std::cout << mymyx1 << std::endl;
std::cout << mymyf1() << std::endl;
return 0;
}

即使我编译并与 uc.cpp 作为库链接,我也会在我的代码中链接未使用的字符串对象。

63: 00000000004041c0    32 OBJECT  GLOBAL DEFAULT   24 _Z11mymystring1B5cxx11
78: 00000000004041a0    32 OBJECT  GLOBAL DEFAULT   24 _Z11mymystring2B5cxx11
90: 0000000000404070     4 OBJECT  GLOBAL DEFAULT   23 mymyx1

使用objdump -h uc.o查看 uc.o 文件内部

11 .data.mymyx2  00000004  0000000000000000  0000000000000000  000001d0  2**2
CONTENTS, ALLOC, LOAD, DATA
12 .data.mymyx1  00000004  0000000000000000  0000000000000000  000001d4  2**2
CONTENTS, ALLOC, LOAD, DATA
13 .bss._Z11mymystring2B5cxx11 00000020  0000000000000000  0000000000000000  000001e0  2**5
ALLOC
14 .bss._Z11mymystring1B5cxx11 00000020  0000000000000000  0000000000000000  000001e0  2**5
ALLOC

我们看到 mymyx1 和 mymyx2 的数据位于单独的数据部分。字符串数据生成 bss 部分?数据在哪里?

好的,我们来看看:objdump -s go

.rodata部分的内容:

402000 62617369 635f7374 72696e67 3a3a5f4d  basic_string::_M
402010 5f636f6e 73747275 6374206e 756c6c20  _construct null 
402020 6e6f7420 76616c69 64005468 69732069  not valid.This i
402030 73206120 76657279 2073696d 706c6520  s a very simple 
402040 74657374 20776861 74206861 7070656e  test what happen
402050 73206966 20666461 74612073 65637469  s if fdata secti
402060 6f6e7320 64696420 6e6f7420 776f726b  ons did not work
402070 21005468 69732076 61726961 626c6520  !.This variable 
402080 73686f75 6c642062 65207573 656400    should be used.

如果我将第二个字符串移动到另一个文件,它将被删除!

.rodata部分的内容:

402000 54686973 20697320 61207665 72792073  This is a very s
402010 696d706c 65207465 73742077 68617420  imple test what 
402020 68617070 656e7320 69662066 64617461  happens if fdata
402030 20736563 74696f6e 73206469 64206e6f   sections did no
402040 7420776f 726b2100                    t work!.  

对我来说,这只是一个编译器/链接器错误!

来自J.舒尔克的回答:

静态成员变量不能仅仅被优化掉,因为它们可以在多个翻译单元中访问。必须编译它们,以便链接器知道何时在不同位置使用相同的静态变量。

它们必须编译,但如果它们从未被访问,则可以在链接阶段删除它们。如上面的代码所示,如果不使用函数和整数变量,则会删除std::string但如果变量与使用的数据位于同一文件中,则不会删除它们。如果我们在单独的文件中移动每个 std::string 变量并通过静态库链接,它们就会被移出。如果我们有-fdata-sections,但在这里不起作用,这也应该发生!为什么?我不知道。