巨大的程序大小C++与标准::正则表达式
Huge program size C++ with std::regex
我想用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
- 使用CMake检测支持的C++标准
- 如何理解C++标准N3337中的expr.const.cast子句8
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 编译标准库类型
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 铸造标准::有没有回到原来的类型
- 标准 N3337 5.2.10 第 7 条中的C++"类型"是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 标准库类型的赋值运算符的引用限定符
- 标准是否严格定义了该程序应该如何编译?
- 如何从Windows应用程序输出到标准?
- 安全到标准:移动会员?
- 如何正确将字符串转换为标准::时间::system_clock::time_point?
- 这是否符合C++标准:双响双响,例如!!(-0.0).
- 标准::变体的赋值运算符
- 捕获标准输出以压缩并使用 CTRL-C 中断会给出损坏的 zip 文件
- 如何在 Mac 上使用 c++17 并行标准库算法?
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- 并行标准::复制复杂性