2016-12-09 67 views
1

最近我一直在分析一些旧代码的某些部分,其中某些情况下,函数返回的值被赋予const变量,有时变为const&。出于好奇心,我已经转向解决问题以了解差异。但要到前点让我得出一个简单的例子,有一些代码来引用:返回值const&和const赋值 - dissassembly

struct Data 
{ 
    int chunk[1024]; 
}; 

Data getData() 
{ 
    return Data(); 
} 

int main() 
{ 
    const std::string varInit{ "abc" }; // 1 
    const std::string varVal = "abc"; // 2 
    const std::string& varRef = "abc"; // 3 

    const Data dataVal = getData(); // 4 
    const Data& dataRef = getData(); // 5 

    return 0; 
} 

上面的代码下面的拆解与禁用优化VS2015收购。

Disassembly for <code>(1)``(2)``(3)</code> 我没有ASM专家,但乍一看我会说,对于(1)(2)有进行类似的操作。尽管如此,令我感到惊讶的是(3)进行了两项额外的操作(leamov),与以前的版本相比,const&在变量赋值过程中未被使用。

通过值从函数返回数据时可以观察到相同情况。 (5)承担了与(4)有关的更多操作。 Disassembly for <code>(4)``(5)</code>

的问题是相当窄:

  • 在哪里,这些额外的操作都来自这里什么是他们的目的是什么?一般来说不像这里:What's the purpose of the LEA instruction,但在上下文中。
  • 这是否会影响性能,至少对于其底层数据大小可忽略不计的对象? (与本例中使用的Data结构相反)
  • 开启优化时会有什么影响吗? (发布版本)

顺便说一句,我已经阅读Why not always assign return values to const reference?有关分配可有点关系,但不是问题的一部分值时,使用常量和const &的利弊。

+1

你的编译器实现'varRef'引用作为一个普通的“变相指针”。 'lea'指令计算该指针的初始值('ecx = ebp-84h'),而'mov'指令将该值保存到'varRef'指针中。 – AnT

回答

4

的情况下,(3)编译器在[ebp-84]创建“隐藏”局部变量“的std :: string”让我们将其命名为_84,做这样的代码

const std::string _84 = "abc"; 
const std::string& varRef = _84;// lea + move (by sense varRef = &_84) 

X& v - 由感和二进制代码相同X* v - v实际指针X在两种情况下,简单地不同的语法使用

相同,并且在情况(5)

const Data _20a0 = getData(); 
const Data& dataRef = _20a0; // by sense dataRef = &_20a0, but by syntax dataRef = _20a0 

或者说,如果你不是行

const Data& dataRef = getData(); 

写行

const Data& dataRef = dataVal; 

您查看此行正好用2汇编指令:

lea eax,[dataVal] 
mov [dataRef],eax 

码(4,5)和Data getData()签名是绝对的噩梦,无字


约“由值”返回结构更加清晰 - 功能只能返回注册为结果(alaxeax和在64 rax)或2个寄存器 - edx:eax(8字节,EDX高)或rdx:rax (在x64中为16字节)

如果是 Data getData() - 不可能返回Data原样。怎么样 ?!?

所以真的是你的函数转换为

Data* getData(Data* p) 
{ 
    Data x; 
    memcpy(p, &x, sizeof(Data)); 
    return p; 
} 

和代码

//const Data dataVal = getData(); 
Data _41A0, _3198, dataVal; 
memcpy(&_3198, getData(&_41A0), sizeof(Data)); 
memcpy(&dataVal, &_3198, sizeof(Data)); 

//const Data& dataRef = getData(); 
Data _41A0, _3198, _20a0, &dataRef; 
memcpy(&_51a8, getData(&_61b0), sizeof(Data)); 
memcpy(&_20a0, &_51a8, sizeof(Data)); 
dataRef = &_20a0;// very small influence compare all other 

尝试计算多少无谓的memcpy做编译器?

需要这样写代码

void InitData(Data* p); 

Data dataVal; 
InitData(&dataVal); 
+0

真的没有理由做'void InitData(Data * p)'这样的事情''当你可以打开优化并让编译器为你做这种转换的时候,它是有利的。哎呀,我无法说服g ++不要在-O1之上的任何内容上调用getData,即使这样RVO也会踢入并构建对象。 –

+0

@MilesBudnek - 是的,现代编译器可以严格优化未优化的源代码。但认为最好的选择 - 理解我们在做什么,理解底线 - 并开始编写更好的代码。功能不可能返回超过2个寄存器大小的值。真的需要传递数据指针。和函数填充这些数据 – RbMm

+1

最好的代码是易于阅读的代码。 'Data dataVal = getData();'清楚地表达了程序员的意图,编译器将为它或'InitData(&dataVal);'生成相同的目标代码。即使在-O0上,g ++也为这两个表达式生成几乎完全相同的代码。我同意知道底下发生了什么是很好的,但知道编译器真的很擅长它也很重要。 –