C++ 引用和数组索引之间有什么区别吗?

C++ 引用和数组索引之间有什么区别吗?,c++,compiler-construction,C++,Compiler Construction,假设您希望访问数组中的结构,并修改几个字段。您可以直接这样做,如: units[unitIndex].field1 = value1; units[unitIndex].field2 = value2; units[unitIndex].field3 = value3; ... 或者,您可以提取对结构的引用,然后从中修改它们: unitStruct& unit = units[unitIndex]; unit.field1 = value1; unit.field2 = value2;

假设您希望访问数组中的结构,并修改几个字段。您可以直接这样做,如:

units[unitIndex].field1 = value1;
units[unitIndex].field2 = value2;
units[unitIndex].field3 = value3;
...
或者,您可以提取对结构的引用,然后从中修改它们:

unitStruct& unit = units[unitIndex];
unit.field1 = value1;
unit.field2 = value2;
unit.field3 = value3;
...
我一直认为第二种方法“更好”,它不必每次都索引到数组中来重新找到结构

但是当我停下来思考时,我意识到编译器很可能将数组索引作为指针来实现。引用也可能被实现为指针。也许他们内部也在做同样的事情


所以我想知道的是,从编译器的角度来看,在生成的代码方面,这两种方法之间有什么真正的区别吗?编译器是否会为其中一种方法生成任何额外的代码,或者它们本质上是两种获得完全相同结果的方法?

在启用优化的情况下编译时,编译器将以任何一种方式生成相同的代码,您应该使用任何使代码对您和代码维护人员更具可读性的样式

比如说

#include <cstddef>
#include <vector>

struct unitStruct {
    int field1;
    int field2;
    int field3;
};

void noref( std::vector<unitStruct> & units, size_t unitIndex, int value1, int value2, int value3 )
{
    units[unitIndex].field1 = value1;
    units[unitIndex].field2 = value2;
    units[unitIndex].field3 = value3;
}

void ref( std::vector<unitStruct> & units, size_t unitIndex, int value1, int value2, int value3 )
{
    unitStruct& unit = units[unitIndex];
    unit.field1 = value1;
    unit.field2 = value2;
    unit.field3 = value3;
}
生成了相同的代码-前三条指令以不同的顺序出现,但仅此而已:

0000000000000000 <_Z5norefRSt6vectorI10unitStructSaIS0_EEmiii>:
   0:   48 8d 04 76             lea    (%rsi,%rsi,2),%rax
   4:   48 8b 37                mov    (%rdi),%rsi
   7:   48 8d 04 86             lea    (%rsi,%rax,4),%rax
   b:   89 10                   mov    %edx,(%rax)
   d:   89 48 04                mov    %ecx,0x4(%rax)
  10:   44 89 40 08             mov    %r8d,0x8(%rax)
  14:   c3                      retq   

0000000000000020 <_Z3refRSt6vectorI10unitStructSaIS0_EEmiii>:
  20:   4c 8b 0f                mov    (%rdi),%r9
  23:   48 8d 04 76             lea    (%rsi,%rsi,2),%rax
  27:   49 8d 04 81             lea    (%r9,%rax,4),%rax
  2b:   89 10                   mov    %edx,(%rax)
  2d:   89 48 04                mov    %ecx,0x4(%rax)
  30:   44 89 40 08             mov    %r8d,0x8(%rax)
  34:   c3                      retq   
0000000000000000:
0:48 8d 04 76 lea(%rsi,%rsi,2),%rax
4:48 8b 37 mov(%rdi),%rsi
7:48 8d 04 86 lea(%rsi,%rax,4),%rax
b:89 10 mov%edx,(%rax)
d:89 48 04 mov%ecx,0x4(%rax)
10:44894008 mov%r8d,0x8(%rax)
14:c3 retq
0000000000000020 :
20:4c 8b 0f mov(%rdi),%r9
23:48 8d 04 76 lea(%rsi,%rsi,2),%rax
27:49 8d 04 81 lea(%r9,%rax,4),%rax
2b:89 10 mov%edx,(%rax)
2d:89 48 04 mov%ecx,0x4(%rax)
30:44894008 mov%r8d,0x8(%rax)
34:c3 retq

我几乎不相信有什么区别。
units[unitIndex]
将为您提供一个unitStruct&


我认为唯一的区别是当有人阅读代码时。

在调试模式下,是的,编译器可以生成不同的代码。对于units[unitIndex],它可以计算每行的units+unitIndex指针。如果使用引用,偏移量将计算一次,并存储在堆栈上

在优化的构建中,在这两种情况下,编译器可能只进行一次计算,并将计算出的偏移量保存在寄存器中

重要的是,它更具可读性和可维护性。当你对一件事进行优化时,你对另一件事感到悲观。在几乎所有情况下,您都应该优化可读性和可维护性


[ETA]如果您真的对差异感到好奇,可以让编译器将生成的程序集写入文件。有关如何执行此操作,请参阅编译器的帮助。

您应该注意,优化是通过-O3启用的。
0000000000000000 <_Z5norefRSt6vectorI10unitStructSaIS0_EEmiii>:
   0:   48 8d 04 76             lea    (%rsi,%rsi,2),%rax
   4:   48 8b 37                mov    (%rdi),%rsi
   7:   48 8d 04 86             lea    (%rsi,%rax,4),%rax
   b:   89 10                   mov    %edx,(%rax)
   d:   89 48 04                mov    %ecx,0x4(%rax)
  10:   44 89 40 08             mov    %r8d,0x8(%rax)
  14:   c3                      retq   

0000000000000020 <_Z3refRSt6vectorI10unitStructSaIS0_EEmiii>:
  20:   4c 8b 0f                mov    (%rdi),%r9
  23:   48 8d 04 76             lea    (%rsi,%rsi,2),%rax
  27:   49 8d 04 81             lea    (%r9,%rax,4),%rax
  2b:   89 10                   mov    %edx,(%rax)
  2d:   89 48 04                mov    %ecx,0x4(%rax)
  30:   44 89 40 08             mov    %r8d,0x8(%rax)
  34:   c3                      retq