指针访问和引用访问哪个更快?

14
在下面的示例代码中,我分配了一些结构体Chunk的实例。在for循环中,我通过使用指针或引用迭代内存块并访问不同的实例,并为它们分配一些随机数据。
但是哪个for循环会执行得最快呢?根据我的知识,我认为引用循环将是最快的,因为它不需要解除引用,并直接访问内存中的实例。我有多错/对?
struct Chunk {
    unsigned int a;
    float b;
    const char* c;
};

int main() {
    Chunk* pData = new Chunk[8];

    for( unsigned int i = 0; i < 8; ++i ) {
        Chunk* p = &pData[i];
        p->a = 1;
        p->b = 1.0f;
        p->c = "POINTERS";
    }

    for( unsigned int i = 0; i < 8; ++i ) {
        Chunk& r = pData[i];
        r.a = 1;
        r.b = 1.0f;
        r.c = "REFERENCES";
    }

    delete [] pData;
    return 0;
}

7
我猜取决于你的编译器,但在我的编译器中,它们编译成完全相同的代码。大多数C ++开发人员更喜欢使用引用作为一种风格习惯。 - James M
请记住,引用就像自动取消引用的指针。 - tadman
@tadman 不符合标准。 - James Kanze
有很多技术细节可以解释它们为什么不完全相同,但一般来说它们的行为方式是如此。这些差异很重要,但在这个例子中并不相关。 - tadman
5个回答

15

它们应该相同(不是关于相同,而是完全相同),适用于任何非白痴编译器。在底层,引用就是指针(在99%的编译器上)。没有任何理由存在差异。

追求严谨:第二个循环可能更快(可能不是)因为数据已经在缓存中了,但也仅此而已。:)


2
不完全正确。引用不是指针,它们根本不是对象。在某些情况下,引用可以由指针实现,但不是100%的情况。指针和引用生成的代码可能会有所不同。 - Rost
12
我没有点踩,尽管我有这个倾向:"references are pointers"是错误的。引用(reference)是别名(alias),可以是指针也可以不是,它们的语义是不同的,标准对它们的处理也是不同的。特别是在上面的代码中,编译器很可能甚至不会为引用在栈中分配空间,而是直接用被引用的对象替换引用。8.3.2/4中说:“一个引用是否需要存储空间是未指定的(3.7)。” 这不是指针的行为。 - David Rodríguez - dribeas
2
@LuchianGrigore 编译器是否使用指针来实现引用,取决于很多因素;大多数编译器在某些情况下会使用指针,但在其他情况下则不会。当然,即使您使用指针,同样的情况也是适用的;如果编译器能够证明指针从未被重新设置,它也可以轻松地抑制指针。 - James Kanze
3
“是与否都有可能。如果编译器可以证明指针和引用满足相同的条件,那么‘好像(as-if)’规则将允许编译器处理它们的方式几乎相同。不同之处在于编译器无需进行任何分析就知道引用不会重新分配地址,不能获取其地址等。在上面这个简单的代码中,这可能没有区别,但在更复杂的情况下,使用引用而不是指针可以帮助编译器。” - James Kanze
2
@LuchianGrigore 很好的观点。任何一个像样的编译器都会完全抑制这两个循环,因为它们中的状态变化对程序输出没有影响。然而,关于你和David的讨论:标准规定引用不是对象,这使它们与指针非常不同。理论上来说,在实践中,我认为你无法区分引用和自动解除引用的指针之间的区别,除了在初始化时:引用表现得好像它是一个自动解除引用的指针。 - James Kanze
显示剩余10条评论

2
我倾向于说:谁在乎呢?速度上的任何差异都微不足道,你应该选择最易读的。在这种特殊情况下,我预计两种情况下会生成完全相同的代码。在更复杂的情况下,编译器可能无法确定在循环后指针未被重新设置,因此可能需要重新读取它。但是,要达到这种情况,你必须进行足够多的其他操作,以至于差异无法被测量。

谢谢您的回答。正如您所注意到的,这并不是一个真实的例子。我只是想知道在更复杂的情况下应该选择什么。 - Michael Mancilla
@MichaelMancilla 无论哪种方式最能表达你想要做的事情。通常情况下,尽可能使用引用,否则使用指针。使用引用告诉读者(和编译器)所引用的对象不会改变。 - James Kanze
4
“Any difference in speed will be negligible,” 的意思是速度上的差异可以忽略不计。而“Your use case is not someone else's.”则是说你的需求和别人的需求不同。 - easytiger

2
执行时间几乎相同,但使用引用不那么繁忙。

你能否更详细地解释一下“不那么繁忙”的含义,以及为什么执行时间几乎相同?请参阅如何撰写一个好的答案 - Jan Schultke
引用只是语法上比指针更好,因此更容易理解和遵循,但就计算机实际处理它们的方式而言,指针和引用基本相同。 引用不像指针一样是变量,它们是常量。 引用仅存在于源代码中,而指针是一个变量并存在于内存中。 在代码中使用引用代替指针可以使代码更加简洁,因此更容易调试,因此对我来说,在代码中使用引用代替指针会比较轻松。 - Keshav jha

1

任何优秀的编译器生成的代码都不应该有差异。


1

当你在两个代码版本之间犹豫不决时,应该选择更易读的那一个。你提出的可能的优化应该由编译器完成。

在你的情况下,更易读的版本是带有引用的版本(实际上,也许并不真正更易读,但共识是要优先使用引用,因为指针更加“危险”)。

但回到效率问题:(如果有人懂汇编,请最好停止阅读,否则你可能会笑出声...)我认为,由于pData是在堆上分配的,编译器无论如何都必须使用指针来编译它。如果你的结构只是用“Chunk data[8];”在栈上分配的话,你的推理可能是正确的。但最迟在启用编译器优化时,差异应该会被消除。


在Linux上,使用g++和clang++编译器,上述代码的结果实际上是相同的。我使用-S -O3选项在两个不同的.cpp文件上进行了测试,并使用汇编注释来跟踪事物。对于g++,我必须将“8”更改为“100”,以便它不会展开“for”循环...对于clang ++,我必须使用std::cout字符串,以防止它优化掉“for”循环... - Andrew
g++ 执行了 leaq, movq, leaq, movq, 然后在循环中,执行了 movq, movl, movq, addq, movl, leaq, movl, cmpq, jne - Andrew
clang++执行了movl指令,然后在循环中执行了movlmovlmovladdljne指令。对于g++和clang++,指针和引用的汇编输出确实是相同的。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接