0

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

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

#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
4

2 回答 2

3

正如 StoryTeller 指针所指出的,_ZN11unusedClass9classNameB5cxx11E不是成员函数,而是成员变量static std::string className

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

使用constexpr

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

constexpr隐含inline函数,隐含变量的“内联性”。由于constexpr变量的值在编译时都是已知的,因此无需将它们存储在编译后的二进制文件中。这就是为什么编译器会在未使用时优化它们,即使在-O0. 这是一个工作示例,其中未编译变量:https ://godbolt.org/z/bqsuTA

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

编辑:如果您使用 astatic 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变量,而是他们自己的副本!

于 2020-05-24T05:45:12.947 回答
2

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

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

查看 uc.o 文件objdump -h 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 部分?数据在哪里?

OK,我们来看一下: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.Schulke 的回答:

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

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

于 2020-05-24T10:57:56.223 回答