巨大的程序大小C++与标准::正则表达式

Huge program size C++ with std::regex

本文关键字:标准 C++ 正则表达式 程序 巨大      更新时间:2023-10-16

我想用std::regex(标准正则表达式库)编译一个小C++程序。
编译器:Fedora 21 上的 gcc/g++ 4.9.2。

#include <string.h>
#include <iostream>
#include <regex>
using namespace std;
int main () {
cout << "Content-Type: text/htmlnn";
  std::string s ("there is a subsequence in the stringn");
  std::regex e ("\b(sub)([^ ]*)");   // matches words beginning by "sub"
  std::cout << std::regex_replace (s,e,"sub-$2");
}

没有-std=c++11就不可能用std::regex编译程序,所以在终端中编译的合适指令是:

g++ -std=c++11 code.cpp -o prog

我的主要问题是:源代码非常小,但为什么编译程序的最终文件大小如此之大:480 KB?

是因为-std=c++11的影响吗?

发生了什么以及如何减小最终二进制程序的大小?

UPD1.

使用 -

Os 标志实际上是使用 std::regex 将程序大小从 480 KB 减少到 100-120 KB 的好方法。但奇怪的是,即使是使用 std::regexp 优化的文件也超过了标准 7-12 kylobytes,用于字符串源代码很少的 C/C++ 程序。例如,可以在 8.5 KB 二进制文件中使用 RE2 正则表达式库(在 Fedora 21 "yum install re2-devel") 中使用相同的正则表达式替换技巧:

#include <string.h>
#include <iostream>
#include "re2/re2.h"
using namespace std;
int main () {
cout << "Content-Type: text/htmlnn";
std::string s ("there is a subsequence in the stringn");
RE2::GlobalReplace(&s, "\b(sub)([^ ]*)", "sub-\2");
cout << s;
}

编译方式:

g++ -lre2 code.cpp -o prog

UPD2.

objdump for std::regex program:

0   .interp 00000013    08048154    08048154    00000154    2**0
1   .note.ABI-tag   00000020    08048168    08048168    00000168    2**2
2   .note.gnu.build-id  00000024    08048188    08048188    00000188    2**2
3   .gnu.hash   000000b0    080481ac    080481ac    000001ac    2**2
4   .dynsym 000006c0    0804825c    0804825c    0000025c    2**2
5   .dynstr 00000b36    0804891c    0804891c    0000091c    2**0
6   .gnu.version    000000d8    08049452    08049452    00001452    2**1
7   .gnu.version_r  000000d0    0804952c    0804952c    0000152c    2**2
8   .rel.dyn    00000038    080495fc    080495fc    000015fc    2**2
9   .rel.plt    000002b8    08049634    08049634    00001634    2**2
10  .init   00000023    080498ec    080498ec    000018ec    2**2
11  .plt    00000580    08049910    08049910    00001910    2**4
12  .text   0001f862    08049e90    08049e90    00001e90    2**4
13  .fini   00000014    080696f4    080696f4    000216f4    2**2
14  .rodata 00000dc8    08069740    08069740    00021740    2**6
15  .eh_frame_hdr   00003ab4    0806a508    0806a508    00022508    2**2
16  .eh_frame   0000f914    0806dfbc    0806dfbc    00025fbc    2**2
17  .gcc_except_table   00000e00    0807d8d0    0807d8d0    000358d0    2**2
18  .init_array 00000008    0807feec    0807feec    00036eec    2**2
19  .fini_array 00000004    0807fef4    0807fef4    00036ef4    2**2
20  .jcr    00000004    0807fef8    0807fef8    00036ef8    2**2
21  .dynamic    00000100    0807fefc    0807fefc    00036efc    2**2
22  .got    00000004    0807fffc    0807fffc    00036ffc    2**2
23  .got.plt    00000168    08080000    08080000    00037000    2**2
24  .data   00000240    08080180    08080180    00037180    2**6
25  .bss    000001b4    080803c0    080803c0    000373c0    2**6
26  .comment    0000002c    00000000    00000000    000373c0    2**0

RE2 程序的 objdump

0   .interp 00000013    08048154    08048154    00000154    2**0
1   .note.ABI-tag   00000020    08048168    08048168    00000168    2**2
2   .note.gnu.build-id  00000024    08048188    08048188    00000188    2**2
3   .gnu.hash   00000034    080481ac    080481ac    000001ac    2**2
4   .dynsym 00000180    080481e0    080481e0    000001e0    2**2
5   .dynstr 00000298    08048360    08048360    00000360    2**0
6   .gnu.version    00000030    080485f8    080485f8    000005f8    2**1
7   .gnu.version_r  000000a0    08048628    08048628    00000628    2**2
8   .rel.dyn    00000010    080486c8    080486c8    000006c8    2**2
9   .rel.plt    00000090    080486d8    080486d8    000006d8    2**2
10  .init   00000023    08048768    08048768    00000768    2**2
11  .plt    00000130    08048790    08048790    00000790    2**4
12  .text   00000332    080488c0    080488c0    000008c0    2**4
13  .fini   00000014    08048bf4    08048bf4    00000bf4    2**2
14  .rodata 00000068    08048c08    08048c08    00000c08    2**2
15  .eh_frame_hdr   00000044    08048c70    08048c70    00000c70    2**2
16  .eh_frame   0000015c    08048cb4    08048cb4    00000cb4    2**2
17  .gcc_except_table   00000028    08048e10    08048e10    00000e10    2**0
18  .init_array 00000008    08049ee4    08049ee4    00000ee4    2**2
19  .fini_array 00000004    08049eec    08049eec    00000eec    2**2
20  .jcr    00000004    08049ef0    08049ef0    00000ef0    2**2
21  .dynamic    00000108    08049ef4    08049ef4    00000ef4    2**2
22  .got    00000004    08049ffc    08049ffc    00000ffc    2**2
23  .got.plt    00000054    0804a000    0804a000    00001000    2**2
24  .data   00000004    0804a054    0804a054    00001054    2**0
25  .bss    00000090    0804a080    0804a080    00001058    2**6
26  .comment    0000002c    00000000    00000000    00001058    2**0

主要区别在于 12.text:在第一种情况下使用的大小 - 0001f862 (129122);第二种情况下 - 仅00000332 (818)。

在 RE2 的情况下,大多数实际实现都在共享库中,它不会成为可执行文件的一部分。当您运行程序时,它会单独加载到内存中。

std::regex的情况下,这实际上只是std::basic_regex<char>的别名,它是一个模板。编译器实例化模板并将其直接构建到程序中。尽管可以在共享库中实例化模板,但它们通常不是,并且在您的情况下,std::basic_regex<char>不在共享库中。

举个例子。以下是使用正则表达式实例化创建单独的共享库的方法:

主.cpp:

#include <iostream>
#include <string>
#include "regex.hpp"
int main () {
  std::cout << "Content-Type: text/htmlnn";
  std::string s {"There is a subsequence in the stringn"};
  std::basic_regex<char> e {"\b(sub)([^ ]*)"};
  std::cout << std::regex_replace (s,e,"sub-$2");
}

正则表达式.hpp:

#include <regex>
extern template class std::basic_regex<char>;
extern template std::string
  std::regex_replace(
    const std::string&,
    const std::regex&,
    const char*,
    std::regex_constants::match_flag_type
  );

正则表达式.cpp:

#include "regex.hpp"
template class std::basic_regex<char>;
template std::string 
  std::regex_replace(
    const std::string&,
    const std::regex&,
    const char*,
    std::regex_constants::match_flag_type
  );

构建步骤:

g++ -std=c++11 -os -c -o main.o main.cppg++ -std=c++11 -os -c -fpic regex.cppg++ -shared -o libregex.so regex.og++ -o main main.o libregex.so -Wl,-rpath,.-L. -lregex

在我的系统上,生成的文件大小为:

       总机:13936libregex.so:196936

首先,正如注释中已经说过的,您应该在执行strip或使用size实用程序后测量优化二进制文件的大小。否则,您会过多关注存储在二进制文件中的调试信息。即使您确实运行该二进制文件,该信息通常也不会占用RAM。

其次,实际的答案——大部分二进制大小来自正则表达式本身和它背后的其他东西std。您可以使用readelf实用程序检查这一点,例如: readelf -sW prog | c++filt — 显示二进制文件中的所有函数及其大小。似乎相当大一部分正则表达式实现被保留为模板函数,这些函数在您的二进制文件中实例化。GCC 作者可能会在 libstdc++ 中实例化更多,以允许共享,就像他们对其他一些事情所做的那样,例如一些string方法。

还有一个不太著名的二进制大小优化技术:ICF(相同的代码折叠)在二进制gold实现。将-fuse-ld=gold -Wl,--icf=safe添加到链接器标志。

您可以通过设置优化来减小大小:

g++ -std=c++11 -O3 -o prog code.cpp

-O3标志表示最大程度的优化。在我的机器中,将可执行文件从519K减少到142K

您还可以使用-Os来优化大小。在我的机器上,进一步将大小减小到120k.

g++ -std=c++11 -Os -o prog code.cpp