未使用的C++未优化的静态成员函数/变量
Unused C++ static member functions/variables not optimized out
我启用了编译器和链接器优化,希望从我的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
,但在这里不起作用,这也应该发生!为什么?我不知道。
- 未使用的C++未优化的静态成员函数/变量
- 内联函数/变量的外部链接
- 同一函数中的静态函数变量初始化顺序
- 将非静态成员函数C 的使用无效,而将成员功能作为函数变量
- 如何从doxygen输出中排除类的无证件成员函数/变量
- 如何在LUA C API中分配全局LUA函数变量
- 如何将函数变量传递给C lambda函数
- 通过另一个C++函数更改函数变量
- c++11 lambda 真的支持闭包吗?函数变量中存在语义冲突
- C++:静态成员不能在声明时定义,但静态函数变量可以?
- apache-frift是否支持函数变量作为参数
- 类函数/变量在使用之前必须声明
- 函数变量去哪里
- 无法使用函数变量?错误表达式必须具有类类型
- 如何在main中使用函数变量
- 每当尝试打印函数变量时,C++ cout 错误
- 是否存在查看成员函数/变量的整洁等效项
- 访问友元类之间的函数变量
- 函数到函数变量
- 一个定义规则是否强制创建一个静态函数变量