对象分配对二进制大小的影响是什么?

时间:2016-05-05 12:30:53

标签: c++ visual-c++

背景

我有一个VS2013解决方案,其中包含许多项目,包含众多来源。 在我的消息来源中,我在源中的不同位置使用相同的宏数千次。

类似的东西:

#define MyMacro(X) X

其中Xconst char*

我有一个DLL项目,上面的宏定义产生了800KB的输出dll大小。

问题

在某些场景或模式中,我希望将宏定义更改为以下内容:

#define MyMacro(X) Utils::MyFunc(X)

此更改产生非常令人不快的副作用,导致DLL输出文件大小增加100KB

备注

  1. Utils::MyFunc()是第一次使用。所以,很自然地,除了二进制文件以外(自从引入新代码以来)增加(一点点)
  2. Utils::MyFunc()不包含大型标头或库。
  3. Utils::MyFunc()确实分配了string个对象。
  4. 使用定义编译所有项目以支持小代码。
  5. 人工实例

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

    问题

    字符串分配是否会对输出文件大小产生如此大的影响?

2 个答案:

答案 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 * 125,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

您可能会发现启用完全优化(发布版本)可让编译器发现这些字符串从未被使用过,并将其删除。也许

相关问题