通过引用传递对象是一种将地址传递给它的更简单,更快速和更安全的方法。 但对于大多数编译器来说,它们完全相同:引用确实是指针。
现在基本类型如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;
}
答案 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
,编译器就像我预期的那样聪明。它理解i
和pi
指向同一个地方,因此它直接使用硬编码的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)