2016-12-09 81 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 ++也爲這兩個表達式生成幾乎完全相同的代碼。我同意知道底下發生了什麼是很好的,但知道編譯器真的很擅長它也很重要。 –