我有一个VS2013
解决方案,其中包含许多项目,包含众多来源。
在我的消息来源中,我在源中的不同位置使用相同的宏数千次。
类似的东西:
#define MyMacro(X) X
其中X
为const char*
我有一个DLL项目,上面的宏定义产生了800KB的输出dll大小。
在某些场景或模式中,我希望将宏定义更改为以下内容:
#define MyMacro(X) Utils::MyFunc(X)
此更改产生非常令人不快的副作用,导致DLL输出文件大小增加100KB 。
Utils::MyFunc()
是第一次使用。所以,很自然地,除了二进制文件以外(自从引入新代码以来)增加(一点点)Utils::MyFunc()
不包含大型标头或库。Utils::MyFunc()
确实分配了string
个对象。#define M1(X) X
#define M2(X) ReturnString1(X)
#define M3(X) ReturnString2(X)
string ReturnString1(const char* c)
{
return string(c);
}
string ReturnString2(const string& s)
{
return string(s);
}
int _tmain(int argc, _TCHAR* argv[])
{
M3("TEST");
M3("TEST");
.
. // 5000 times
.
M3("TEST");
return 1;
}
在上面的例子中,我生成了一个小的EXE项目,试图模仿我面临的问题。
在M1
中专门使用_tmain
- 编译是即时的,输出文件是88KB EXE。
在M2
中专门使用_tmain
- 编译需要几分钟,输出文件为239KB EXE
在M3
中专门使用_tmain
- 编译需要更长时间,输出文件为587KB EXE。
我使用IDA来比较二进制文件并从二进制文件中提取函数名称。
在M2
& M3
,我看到了比M1
中看到的更多以下功能:
... $basic_string@DU?$char_traits@D@std@@V?$allocator@...
我对M2
& M3
我正在分配一个string
对象。
但这足以证明151KB&增加499KB?
字符串分配是否会对输出文件大小产生如此大的影响?
答案 0 :(得分:1)
这是另一个“人为”的例子:
int main()
{
const char* p = M1("TEST");
std::cout << p;
string s = M3("TEST");
std::cout << s;
return 1;
}
我一次评论一个部分并查看生成的ASM。对于M1
宏,我得到了:
012B1000 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (012B204Ch)]
012B1006 call std::operator<<<std::char_traits<char> > (012B1020h)
012B100B mov eax,1
适用于M3
:
00DC1068 push 4
00DC106A push ecx
00DC106B lea ecx,[ebp-40h]
00DC106E mov dword ptr [ebp-2Ch],0Fh
00DC1075 mov dword ptr [ebp-30h],0
00DC107C mov byte ptr [ebp-40h],0
00DC1080 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (0DC1820h)
00DC1085 lea edx,[ebp-40h]
00DC1088 mov dword ptr [ebp-4],0
00DC108F lea ecx,[s]
00DC1092 call ReturnString2 (0DC1000h)
00DC1097 mov byte ptr [ebp-4],2
00DC109B mov eax,dword ptr [ebp-2Ch]
00DC109E cmp eax,10h
00DC10A1 jb main+6Dh (0DC10ADh)
00DC10A3 inc eax
00DC10A4 push eax
00DC10A5 push dword ptr [ebp-40h]
00DC10A8 call std::_Wrap_alloc<std::allocator<char> >::deallocate (0DC17C0h)
00DC10AD mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0DC3050h)]
00DC10B3 lea edx,[s]
00DC10B6 mov dword ptr [ebp-2Ch],0Fh
00DC10BD mov dword ptr [ebp-30h],0
00DC10C4 mov byte ptr [ebp-40h],0
00DC10C8 call std::operator<<<char,std::char_traits<char>,std::allocator<char> > (0DC1100h)
00DC10CD mov eax,dword ptr [ebp-14h]
00DC10D0 cmp eax,10h
00DC10D3 jb main+9Fh (0DC10DFh)
00DC10D5 inc eax
00DC10D6 push eax
00DC10D7 push dword ptr [s]
00DC10DA call std::_Wrap_alloc<std::allocator<char> >::deallocate (0DC17C0h)
00DC10DF mov eax,1
查看第一列(地址),M1
代码大小为12,而M3
- 119。
我会留下它作为练习让读者找出5,000 * 12
和5,000 * 119
之间的区别:)
答案 1 :(得分:0)
让我们以一个简单的例子来看两个案例:
int _tmain()
{
"TEST";
std::string("TEST");
}
第一个声明没有任何效果,并且很容易被优化掉。
第二个语句构造一个字符串,需要函数调用。但是什么功能被称为?也许它是字符串构造函数,但如果内联,可能实际上是malloc()
,strlen()
和memcpy()
直接从main调用(不是明确的,但这三个函数可能是合理的由字符串构造函数使用,可以是内联的。
现在,如果你有这个:
std::string("TEST");
std::string("TEST");
std::string("TEST");
你可以看到它不是3个函数调用,而是9个(在我们的假设中)。如果您确定要调用的函数不内联(使用__declspec(noinline)
或在单独的翻译单元中定义它,也就是.cpp文件),您可以将其恢复为3
您可能会发现启用完全优化(发布版本)可让编译器发现这些字符串从未被使用过,并将其删除。也许