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

时间:2011-10-31 18:51:32

标签: c++ pointers reference arguments pass-by-reference

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

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

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

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

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

int foo(const int i)
{
   cout << i;
}

顺便说一句,在某些情况下,传递i&i,然后使用i进行阅读,*i进行写作甚至更快。< / p>

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

4 个答案:

答案 0 :(得分:4)

我可以相信他们可以把这个编成这个吗? 是的你可以。[这里的意思不同,请阅读编辑部分,澄清的是]

int foo(const int & i)

告诉编译器i是对常量整数类型的引用 编译器可以执行优化,但只允许它们按照 As-If规则执行优化。因此,您可以放心,对于您的程序,上述行为将与(const限定符将得到尊重)一样好:

int foo(const int i)

As-If规则:

  

C ++标准允许编译器执行任何优化,只要生成的可执行文件具有相同的可观察行为,就好像满足了标准的所有要求一样。

对于Standerdese粉丝:
C ++ 03 1.9“程序执行:

  

符合实现需要模拟(仅)抽象机器的可观察行为。

Foot-Note说:

  

这项规定有时被称为“假设”规则,因为只要结果好像符合要求,实施可以自由地忽视本国际标准的任何要求,只要可以从程序的可观察行为。例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且没有产生影响程序的可观察行为的副作用。

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

答案 1 :(得分:3)

它不应该将其编译成因为它可能不正确。考虑:

int foo(const int &i, int *p)
{
   *p = 42;
   cout << i; // prints 42
   return 0;
}

int main()
{
   int x = 5;
   foo(x, &x);
   return 0;
}

int foo(const int i, int *p)
{
   *p = 42;
   cout << i; // prints 5
   return 0;
}

int main()
{
   int x = 5;
   foo(x, &x);
   return 0;
}

编译器如何知道这不会发生?它必须以某种方式能够分析不可能访问该变量来改变它,例如(1)某人有指针,(2)它可能是一个全局变量,(3)来自另一个线程。鉴于C的不安全性,使用指针算法和所有,甚至保证函数无法获得指向变量的指针可能是不可能的。

答案 2 :(得分:0)

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;
}

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)

嗯,ASM是完全相同的,无疑已经进行了优化。

<强> 2。通过i&i

让我们在这里考虑@newacct的anser。

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;
}

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) 

ok1中,我无法看到它在pi中写入2。可能它理解无论如何都会被函数的结果覆盖,因此写作是没用的。

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

备注:

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

答案 3 :(得分:0)

gcc似乎没有使用-O3(gcc版本4.7.2)进行此优化。使用Gabriel的代码,注意ok2在索引到变量之前如何加载解除引用的地址,而ok1则没有。

OK1:


    .cfi_startproc
    subq    $40, %rsp
    .cfi_def_cfa_offset 48
    movslq  %edi, %rdi
    fildl   vars(,%rdi,4)
    fld %st(0)
    fsqrt
    fucomi  %st(0), %st
    jp  .L7
    fstp    %st(1)
OK2:

    .cfi_startproc
    subq    $40, %rsp
    .cfi_def_cfa_offset 48
    movslq  (%rdi), %rax
    fildl   vars(,%rax,4)
    fld %st(0)
    fsqrt
    fucomi  %st(0), %st
    jp  .L12
    fstp    %st(1)