C++ 返回值常量&;和常量赋值-disassembly
最近我一直在分析旧代码的某些部分,在某些情况下,函数返回的值被分配给C++ 返回值常量&;和常量赋值-disassembly,c++,disassembly,C++,Disassembly,最近我一直在分析旧代码的某些部分,在某些情况下,函数返回的值被分配给const变量,有时分配给const&。出于好奇,我换成了dissasembly,看看其中的区别。但在说到重点之前,让我先画一个简单的示例,其中有一些代码可供参考: struct Data { int chunk[1024]; }; Data getData() { return Data(); } int main() { const std::string varInit{ "abc" }; //
const
变量,有时分配给const&
。出于好奇,我换成了dissasembly,看看其中的区别。但在说到重点之前,让我先画一个简单的示例,其中有一些代码可供参考:
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获得的
我不是asm专家,但乍一看,我认为对于(1)
和(2)
有类似的操作。然而,与以前的版本相比,const&
在变量值赋值过程中没有使用,lea
和mov
),我很惊讶(3)
携带了两个额外的操作(lea
和mov
)
当数据按值从函数返回时,也可以观察到同样的情况(5)
执行与(4)
相关的另外两个操作。
问题很窄:
- 这些额外的操作从何而来?它们的目的是什么?一般来说不像这里:而是在呈现的上下文中
- 至少对于基线数据大小可以忽略不计的对象,这是否会影响性能?(与示例中使用的
struct相反)Data
- 当优化打开时,这会有任何影响吗?(用于发布版本)
[ebp-84]
处创建“隐藏的”局部变量std::string”,让它命名为\u 84
,然后这样编码
const std::string _84 = "abc";
const std::string& varRef = _84;// lea + move (by sense varRef = &_84)
void InitData(Data* p);
Data dataVal;
InitData(&dataVal);
与X*v
-v
相同的X&v
-在这两种情况下实际上都指向X
,只是使用了不同的语法
与第(5)种情况相同
或者说如果你改为排队
const Data& dataRef = getData();
写行
const Data& dataRef = dataVal;
您认为此行正好包含两条asm指令:
lea eax,[dataVal]
mov [dataRef],eax
代码(4,5)和datagetdata()
signature绝对是噩梦,没有文字
为了更清楚 关于返回结构“按值”-函数只能返回寄存器作为结果(
al
、ax
、eax
和rax
在x64中)或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
需要这样写代码吗
const std::string _84 = "abc";
const std::string& varRef = _84;// lea + move (by sense varRef = &_84)
void InitData(Data* p);
Data dataVal;
InitData(&dataVal);
在案例(3)中,编译器在[ebp-84]
处创建“隐藏的”局部变量“std::string”,并将其命名为\u 84
,然后执行如下代码
const std::string _84 = "abc";
const std::string& varRef = _84;// lea + move (by sense varRef = &_84)
void InitData(Data* p);
Data dataVal;
InitData(&dataVal);
与X*v
-v
相同的X&v
-在这两种情况下实际上都指向X
,只是使用了不同的语法
与第(5)种情况相同
或者说如果你改为排队
const Data& dataRef = getData();
写行
const Data& dataRef = dataVal;
您认为此行正好包含两条asm指令:
lea eax,[dataVal]
mov [dataRef],eax
代码(4,5)和datagetdata()
signature绝对是噩梦,没有文字
为了更清楚 关于返回结构“按值”-函数只能返回寄存器作为结果(
al
、ax
、eax
和rax
在x64中)或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
需要这样写代码吗
const std::string _84 = "abc";
const std::string& varRef = _84;// lea + move (by sense varRef = &_84)
void InitData(Data* p);
Data dataVal;
InitData(&dataVal);
编译器将
varRef
引用实现为普通的“伪装指针”。lea
指令计算该指针的初始值(ecx=ebp-84h
),而mov
指令将该值保存到varRef
指针中。编译器将varRef
引用实现为普通的“伪装指针”。lea
指令计算该指针的初始值(ecx=ebp-84h
),而mov
指令将该值保存到varRef
指针中当您可以打开优化并让编译器在有利的时候为您进行转换时。见鬼,我无法说服g++不要在-O1以上的任何东西上内联调用getData
,甚至在RVO介入并在适当的位置构造对象。@MilesBudnek-是的,现代编译器可以严重优化未优化的源代码。但是,最好的选择是——了解我们在做什么,了解我们在做什么——然后开始编写更好的代码。函数返回的值不可能超过2个寄存器大小。确实需要将指针传递到数据。最好的代码是易于阅读的代码<代码>数据dataVal=getData()代码>清楚地表达了程序员的意图,编译器将为其生成相同的目标代码或InitData(&dataVal)代码>。即使是on-o0g++也会为这两个表达式生成几乎完全相同的代码。我同意知道引擎盖下发生了什么很好,但是知道编译器真的很擅长它的工作也很重要。谢谢,很好的“幕后”解释。我已经检查了const Data&dataRefFromVal=dataVal代码>它实际上只接受上面提到的2条指令-这就是我所缺少的。在我看来,在RVO和复制省略时代,通常使用const&
而变量赋值在