Question

Let's say you want to access a struct within an array, and modify several fields. You could do so directly, as:

units[unitIndex].field1 = value1;
units[unitIndex].field2 = value2;
units[unitIndex].field3 = value3;
...

Or, you could pull a reference to the struct, and then modify them from there:

unitStruct& unit = units[unitIndex];
unit.field1 = value1;
unit.field2 = value2;
unit.field3 = value3;
...

I always assumed that the second method was "better", that it wouldn't have to index into the array every time to re-find the struct.

But then when I stopped to think about it, I realized that the compiler is likely implementing the array index as a pointer. And the reference also is likely to be implemented as a pointer. Maybe they are internally doing the same thing?

So what I'm wondering is, from a compiler perspective in terms of generated code, is there any real difference between these two methods? Does the compiler generate any extra code for one of these methods, or are they essentially two ways of getting the exact same result?

Was it helpful?

Solution

When compiled with optimization enabled the compiler will generate the same code either way, you should use whatever style makes the code more readable to you and the people that maintain the code.

For example

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

Compiled with gcc and optimization enabled -O3

g++ -O3 -c struct.cpp -o struct.o
objdump -D struct.o|less

the same code was generated - the first three instructions appear in different order, but that's it:

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   

OTHER TIPS

In debug mode, yes, the compiler can generate different code. For units[unitIndex] it may calculate the units + unitIndex pointer for each line. If you use the reference, the offset will be calculated once, and stored on the stack.

In an optimized build, in both cases the compiler will probably do the calculation once and keep the calculated offset in a register.

The important thing is, which is more readable and more maintainable. When you optimize for one thing, you're pessimizing for something else. In almost all cases, you should optimize for readability and maintainability.

[ETA] If you're really curious about the difference, you can have the compiler write the generated assembly to a file. See the help for your compiler on how to do this.

I hardly believe there is any difference. units[unitIndex] will give you a unitStruct&.

I think the only difference come when someone read the code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top