gcc:剥离未使用的函数

gcc: Strip unused functions

提问人:John London 提问时间:11/29/2016 最后编辑:John London 更新时间:12/30/2016 访问量:6746

问:

我注意到,有时即使我不使用相关的 I/O 库,我由 Mingw 生成的二进制文件仍然大得不合理。iostream

例如,我写了一个代码来使用,并且只用它编译它,我的程序可以达到 2MB!我跑了,震惊地看到其中的所有相关功能。vectorcstdio-O2 -fltonm main.exe > e.txtiostream

经过一番谷歌搜索,我学会了使用 ,它将程序大小从 2MB 减少到 ~300KB(如果有 ,100+KB)。非常好!-ffunction-sections -Wl,-gc-sections-s

为了进一步测试 的效果,这里是另一个代码:-ffunction-sections -Wl,-gc-sections

#include <cstdio>
#include <vector>
#include <tuple>
#include <algorithm>
#include <chrono>
#include <windows.h>

#undef min

struct Point {
    int x, y;
};

constexpr int length = 5;
constexpr int half_length() {
    return length & 1 ? length : length - 1;
}

template<class F>
int func_template(F&& f) {
#ifdef _MSC_VER
    puts(__FUNCSIG__);
#else
    puts(__PRETTY_FUNCTION__);
#endif
    printf("\n");
    return f();
}

struct fake_func {
    int operator()() const { return 59; };
};

template<class F, class... Args>
int pass_args(F&& f, Args&&... args) {
#ifdef _MSC_VER
    puts(__FUNCSIG__);
#else
    puts(__PRETTY_FUNCTION__);
#endif
    printf("\n");
    return f(std::forward<Args>(args)...);
}

template<class T>
T min(T x) {
    return x;
}

template<class T, class... Args>
T min(T x, Args... args) {
    T y = min(args...);
    return x < y ? x : y;
}

void type_verifier(int x) {
    printf("%dd ", x);
}

void type_verifier(char x) {
    printf("'%c' ", x);
}

void type_verifier(double x) {
    printf("%lff ", x);
}

template<class T>
void type_verifier(T x) {
    printf("unknown ");
}

template<class T, class... Args>
void type_verifier(T x, Args... args) {
    type_verifier(x);
    type_verifier(args...);
}

int bufLen;
char buf[100];

template<class... Args>
inline int send(Args... args) {
    bufLen = sprintf(buf, std::forward<Args>(args)...);
    return bufLen;
}

namespace std {

inline namespace v1 {
    void func() {
        printf("I am v1\n");
    }
}

namespace v2 {
    void func() {
        printf("I am v2\n");
    }
}

}

int main() {
    std::vector<int> v {1, 2, 3, 4, 5};
    for (auto &i : v) printf("%d ", i);
    printf("\n");

    Point p {1, 2};
    printf("%d %d\n", p.x, p.y);

    auto t = std::make_tuple("Hello World", 12);
    printf("%s %d\n", std::get<0>(t), std::get<1>(t));
    int a, b;
    auto f = []() { return std::make_tuple(1, 2); };
    std::tie(a, b) = f();
    printf("%d %d\n", a, b);

    //int test_constexpr[half_length() + 4];

    int ft = func_template([]{ return 42; });
    printf("func_template: %d\n", ft);
    ft = func_template(fake_func {});
    printf("func_template: %d\n", ft);
    ft = pass_args([](int x, int y) { return x + y; }, 152, 58);
    printf("pass_args: %d\n", ft);
    ft = pass_args([](int n, const char *m) {
        for (int i = 0; i < n; i++) printf("%c ", m[i]);
        printf("\n");
        return 0;
    }, 5, "Hello");

    printf("min: %d\n", min(3, 4, 2, 1, 5));
    type_verifier(12, 'A', 0.5, "Hello");
    printf("\n");

/*  send("Hello World");
    send("%d", 1);
    send("%d", "1234");
    sprintf(buf, "%d", "123");*/

    std::func();
    std::v1::func();
    std::v2::func();

    std::rotate(v.begin(), v.begin() + 2, v.end());
    for (auto &i : v) printf("%d ", i);
    printf("\n");

    auto start = std::chrono::steady_clock::now();

    std::vector<int> x {2, 4, 2, 0, 5, 10, 7, 3, 7, 1}; 
    printf("insertion sort: ");
    for (auto &i: x) printf("%d ", i);
    printf("\n");
    // insertion sort
    for (auto i = x.begin(); i != x.end(); ++i) {
        std::rotate(std::upper_bound(x.begin(), i, *i), i, i+1);
        for (auto &j: x) printf("%d ", j);
        printf("\n");
    }

    std::vector<int> heap {7, 5, 3, 4, 2};
    std::make_heap(heap.begin(), heap.end());
    std::pop_heap(heap.begin(), heap.end());
    printf("Pop heap (%d)\n", heap.back());
    heap.pop_back();
    heap.push_back(1);
    std::push_heap(heap.begin(), heap.end());
    std::sort_heap(heap.begin(), heap.end());
    for (auto &i: heap) printf("%d ", i);
    printf("\n");

    auto end = std::chrono::steady_clock::now();
    auto diff = end - start;
    printf("time: %I64d ms\n",
        std::chrono::duration_cast<std::chrono::milliseconds>(diff).count());

    {
        auto u = v;
        std::move_backward(u.begin(), u.begin() + u.size() - 1, u.begin() + u.size());
        for (auto &i : u) printf("%d ", i);
        printf("\n");
    }

    {
        auto u = v;
        std::move(u.begin() + 1, u.begin() + u.size(), u.begin());
        for (auto &i : u) printf("%d ", i);
        printf("\n");
    }

    start = std::chrono::steady_clock::now();
    Sleep(2000);
    end = std::chrono::steady_clock::now();
    diff = end - start;
    printf("time: %I64d ms\n",
        std::chrono::duration_cast<std::chrono::milliseconds>(diff).count());

    std::chrono::steady_clock::time_point before;
    before = std::chrono::steady_clock::now();
    Sleep(2000);
    auto after = std::chrono::steady_clock::now();
    printf("%f seconds\n", std::chrono::duration<double>(after - before).count());

    return 0;
}

令我失望的是,最终程序再次> 2MB。

有趣的是,即使我没有使用或任何其他标志,也要深思熟虑地始终如一地删除所有相关函数,只是.(对于上面的代码,生成 100+KB 二进制文件)。cl.exeiostream/O2cl.exe main.cppcl.exe

我是否为此错过了任何其他有用的 gcc 标志?

规范:

  • 明格-w64 gcc 6.1.0
  • 明格-w64 gcc 6.2.0
  • Visual Studio 2017 RC
  • 所有二进制文件都是静态链接的

与 Linux 比较

我比较了 gcc 4.9.2 (Linux) 和 gcc 4.9.3 (mingw-w64) 生成的二进制文件,用于上述代码(除了和被删除)。windows.hSleep

编译标志

g++ -o c++11 c++11.cpp -std=c++11 -static-libgcc -static-libstdc++ -ffunction-sections -Wl,-gc-sections -O2

Linux gcc 确实成功地剥离了不需要的功能,而 Mingw-w64 gcc 却无法正确完成。iostream-flto

Windows 仅支持 PE 格式,而 Linux 支持 ELF 格式,允许 Linux 使用 Gold 链接器。也许这就是解释?

更新

我最终在 https://sourceforge.net/p/mingw-w64/bugs/578/ 提交了一个错误。让我们希望它能引起一些关注!

C 工作室 GCC 可视化 C++ IOSTREAM

评论

1赞 benbuck 12/27/2016
这可能会有所帮助: 如何使用 GCC 和 ld 删除未使用的 C/C++ 符号?
0赞 John London 12/27/2016
尝试了所有方法:-Os(减少 2KB)、-fwhole-program(无更改)、-fomit-frame-pointer(无更改)。-why_live 不可用。
0赞 phuclv 12/30/2016
length & 1 ? length : length - 1可以更改为length + (length & 1) - 1
1赞 Michel Rouzic 8/7/2018
对我来说,使用 MSYS8.2.0 的 gcc 2 做到了。-fwhole-program
0赞 benjarobin 10/18/2019
查看错误报告 sourceware.org/bugzilla/show_bug.cgi?id=11539,其中包含正在进行的补丁(自几年前以来没有更新......

答:

4赞 yugr 11/30/2016 #1

尝试通过 从静态 libstdc++ 中剥离调试和符号信息。这使我在 Cygwin (13x) 上将可执行文件从 9M 减少到 670K,在 Ubuntu (80x) 上从 6M 减少到 80K。-Wl,--strip-all

评论

0赞 John London 11/30/2016
-Wl,--strip-all和我上面提到的一样。剥离调试信息和符号表不会剥离未使用的相关函数,这是 MSVC 在不告知的情况下做的事情(不需要特殊标志)!-siostream
0赞 yugr 11/30/2016
好的,我明白了。仅供参考,4.8.2 x86_64-w64-mingw32-g++ (Ubuntu 14.04) 生成 100K 可执行文件,因此大小问题可能特定于特定工具链版本(因此更难远程调查)。您是否尝试过分析链接器映射()?-std=c++11 -O2 -ffunction-sections -Wl,-gc-sections -static-libstdc++ -s-Wl,--print-map
0赞 John London 11/30/2016
由于我使用最新的 MSVC,因此为了公平起见,我更改为 gcc 6.2.0。 表明 GCC 确实剥离了一些和功能,但表明仍有剩余部分。MSVC:150KB,GCC:738KB。查看 pastebin.com/raw/4uGCm7Yy-Wl,-print-gc-sectionsiostreamlocale-Wl,--print-map
0赞 yugr 12/1/2016
我认为您附加了 gc.txt 而不是 map.txt(转储包含来自 -print-gc-sections 的输出)。“gcc: 738KB” - 我很困惑,原来你说它超过 2M......
0赞 yugr 12/1/2016
对 PE/COFF 目标(即 MinGW/Cygwin)的 FWIW 实验支持仅在 2015 年最短的 Bintools 2.25 中登陆,因此它可能是次优的。--gc-sections