主流编译器是否将传递引用的基本类型转换为pass-by-copy?

Gab*_*iel 7 c++ pointers arguments reference pass-by-reference

通过引用传递对象是将地址传递给它的更简单,更快速和更安全的方式.但是对于大多数编译器来说,它们完全相同:引用确实是指针.

现在基本类型int怎么样?将地址传递int给函数并在函数内部使用它比复制传递要慢,因为在使用之前需要取消引用指针.

现代编译器如何处理,这个?

int foo(const int & i)
{
   cout << i; // Do whatever read-only with i.
}
Run Code Online (Sandbox Code Playgroud)

我可以相信他们将此编译成这个吗?

int foo(const int i)
{
   cout << i;
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,在某些情况下,甚至可能会更快地同时通过i&i,然后用i阅读和*i写作.

int foo(const int i, int * ptr_i)
{
   cout << i;    // no dereferencement, therefore faster (?)
   // many more read-only operations with i.
   *ptr_i = 123;
}
Run Code Online (Sandbox Code Playgroud)

Alo*_*ave 5

我可以相信他们将其汇编成此吗?
是的,可以。[这里的“是”的含义有所不同,请阅读“编辑”部分,其中有明确说明)

int foo(const int & i)
Run Code Online (Sandbox Code Playgroud)

告诉编译器i是对常量整数类型的引用。
编译器可以执行优化,但是仅允许他们遵循“ 如果规则”进行优化。因此,您可以放心,对于您的程序,上面的行为将与(const遵守限定符的)行为一样好:

int foo(const int i)
Run Code Online (Sandbox Code Playgroud)

假设规则:

C ++标准允许编译器执行任何优化,只要生成的可执行文件表现出与标准的所有要求均已满足相同的可观察行为即可。

对于标准狂热者:
C ++ 03 1.9“程序执行:

需要遵循一致的实现来(仅)模拟抽象机的可观察行为。

脚注说:

该规定有时称为“按原样”规则,因为只要可以从可观察到的行为中确定结果,就可以无视本国际标准的任何要求,而该实现可以自由地执行。该程序。例如,如果实际实现可以推断出未使用其值并且不会产生影响程序可观察行为的副作用,则无需评估表达式的一部分。

编辑:
由于对答案有一些困惑,让我澄清一下:
不能在编译器上强制执行优化。所以编译器如何解释它取决于编译器。重要的是程序的可观察行为不会改变。


Gab*_*iel 1

至少在我测试过的简单情况下,Visual Studio 2010 (Express) 确实如此。有人要测试gcc吗?

我已经测试了以下内容:

1.仅通过i

int vars[] = {1,2,3,12,3,23,1,213,231,1,21,12,213,21321,213,123213,213123};

int ok1(const int i){
    return sqrtl(vars[i]);
}

int ok2(const int & i){
    return sqrtl(vars[i]);
}

void main() {
    int i;
    std::cin >> i;
    //i = ok1(i);
    i = ok2(i);
    std::cout << i;
}
Run Code Online (Sandbox Code Playgroud)

ASM:

i = ok1(i);
000D1014  mov         ecx,dword ptr [i]  
000D1017  fild        dword ptr vars (0D3018h)[ecx*4]  
000D101E  call        _CIsqrt (0D1830h)  
000D1023  call        _ftol2_sse (0D1840h) 

i = ok2(i);
013A1014  mov         ecx,dword ptr [i]  
013A1017  fild        dword ptr vars (13A3018h)[ecx*4]  
013A101E  call        _CIsqrt (13A1830h)  
013A1023  call        _ftol2_sse (13A1840h)
Run Code Online (Sandbox Code Playgroud)

嗯,ASM 是相同的,毫无疑问进行了优化。

2. 通过i&i

让我们考虑一下 @newacct 的答案。

int vars[] = {1,2,3,12,3,23,1,213,231,1,21,12,213,21321,213,123213,213123};

int ok1(const int i, int * pi) {
    *pi = 2;
    return sqrtl(vars[i]);
}

int ok2(const int & i, int * pi) {
    *pi = 2;
    return sqrtl(vars[i]);
}

void main() {
    int i;
    int * pi = &i;
    std::cin >> i;
    i = ok1(i, pi);
    //i = ok2(i, pi);
    std::cout << i;
}
Run Code Online (Sandbox Code Playgroud)

ASM:

i = ok1(i, pi);
00891014  mov         ecx,dword ptr [i]
00891017  fild        dword ptr vars (893018h)[ecx*4] // access vars[i] 
0089101E  call        _CIsqrt (891830h)  
00891023  call        _ftol2_sse (891840h)  

i = ok2(i, pi);
011B1014  fild        dword ptr [vars+8 (11B3020h)]   // access vars[2]
011B101A  call        _CIsqrt (11B1830h)  
011B101F  call        _ftol2_sse (11B1840h) 
Run Code Online (Sandbox Code Playgroud)

ok1我看不到它将 2 写入pi. 可能它明白无论如何该内存位置都会被函数的结果覆盖,所以写入是没有用的。

有了ok2,编译器就如我预期的那样聪明了。它理解这一点ipi指向同一个地方,因此它2直接使用硬编码。

笔记:

  • 我为这两个测试编译了两次,一次仅取消注释ok1,一次仅取消注释ok2。同时编译这两个函数会导致两个函数之间的优化更加复杂,最终导致所有内联和混合
  • 我在数组中添加了一个查找vars,因为简单的调用sqrtl被简化为基本的类似 ADD 和 MUL 的操作,而无需实际调用
  • 已编译发布
  • 当然达到了预期的效果