C++ C++;:为什么对于内置(即类似C的)类型,按值传递通常比按引用传递更有效

C++ C++;:为什么对于内置(即类似C的)类型,按值传递通常比按引用传递更有效,c++,pass-by-reference,pass-by-value,C++,Pass By Reference,Pass By Value,正如标题所示,编译器供应商通常会将引用实现为指针。指针的大小往往与许多内置类型相同或更大。对于这些内置类型,无论是通过值还是通过引用传递,都会传递相同数量的数据。但是,在函数中,为了获得实际数据,您需要取消对该内部指针的引用。这可以向生成的代码中添加一条指令,并且您还有两个可能不在缓存中的内存位置。差别不会很大,但可以通过紧密的循环来衡量 编译器供应商可以选择在内置类型上使用常量引用(有时也包括非常量引用)时忽略它们——所有这些都取决于编译器处理函数及其调用方时可用的信息 对于int、char、

正如标题

所示,编译器供应商通常会将引用实现为指针。指针的大小往往与许多内置类型相同或更大。对于这些内置类型,无论是通过值还是通过引用传递,都会传递相同数量的数据。但是,在函数中,为了获得实际数据,您需要取消对该内部指针的引用。这可以向生成的代码中添加一条指令,并且您还有两个可能不在缓存中的内存位置。差别不会很大,但可以通过紧密的循环来衡量


编译器供应商可以选择在内置类型上使用常量引用(有时也包括非常量引用)时忽略它们——所有这些都取决于编译器处理函数及其调用方时可用的信息

对于int、char、short和float等pod类型,数据的大小与传入以引用实际数据的地址的大小相同(或更小)。查找引用地址的值是不必要的步骤,并且会增加额外的成本

例如,使用以下函数
foo
bar

void foo(char& c) {...}
void bar(char c) {...}
调用
foo
时,根据您的平台,通过32位或64位的值传递地址。当您在
foo
中使用
c
时,您需要查找传入地址中保存的数据值


在调用<代码> bar >代码>时,字符的大小被传递进去,没有地址查找开销。

在实践中,C++实现一般通过在引擎盖下传递指针(假设调用没有内联)实现逐个引用。 因此,没有一种聪明的机制可以让引用传递更快,因为传递指针并不比传递一个小值快。一旦你在函数中,传递值也可以从更好的优化中受益。例如:

int foo(const int &a, int *b) {
    int c = a;
    *b = 2;
    return c + a;
}
编译器知道,
b
指向
a
,这称为“别名”。如果通过值传递
a
,则此函数可以优化为等效于
*b=2;返回2*a。在现代CPU的指令管道中,这可能更像“开始加载,开始存储b,等待a加载,乘以2,等待b存储,返回”,而不是“开始加载,开始存储b,等待a加载,等待b存储,开始加载,等待a加载,将a添加到c,返回”,您将开始了解为什么潜在的别名会对性能产生重大影响。在某些情况下,如果不一定会产生巨大的影响


当然,别名只会在某些可能的输入改变函数效果的情况下妨碍优化。但是,仅仅因为您对函数的意图是别名不应该影响结果,并不一定意味着编译器可以假设它不会影响结果:事实上,有时在您的程序中,没有出现别名,但编译器不知道这一点。而且不必有第二个指针参数,每当函数调用优化器“看不到”的代码时,它必须假设任何引用都可能更改。

通常较小类型的实际传递数据将与CPU的本机字长相对应。@Erik:当它没有(
char
)时,它不是升级为更宽的类型(
16
bits)以便通过寄存器传递吗?@Matthieu M:它通常也升级为寄存器的完整大小-在x86 32机器上,它将被传递,例如EAX而不是AX或AH/AL,EAX更快。所有这些都取决于courseIt的编译器。值得注意的是,对于内联函数,通过引用传递有时比通过值传递快一点,因为它可以简单地用传入的左值替换参数。在某些情况下,编译器可能能够在无法更改语义时替换pass-by-reference,但是知道这种替换何时可以更改代码语义并不总是那么容易。