2

I tend to use const reference parameters when calling functions assuming this would be efficient since copies of the same wouldn't not be made. Accidentally I changed the function parameter in a function that previously had a const reference parameter to const now, and I observed that the code size is reduced upon compilation.

To check this, I looked into the assembly of a MWE:

#include <cstdint>

void foo(const int n)
{
    int a = n + 1;
}

void foo(const int& n)
{
    int a = n + 1;
}

On line 19 of the generated assembly code, I see an additional step ldr r3, [r3] in the case of void foo(const int& n) when compared to void foo(const int n). I assume this is the reference to variable n (I could be wrong, please forgive me).

So, my question is, why is the code larger when passing via reference and also what method is efficient?

mmcblk1
  • 158
  • 1
  • 3
  • 10
  • What optimization level? – 0x5453 Aug 03 '22 at 18:05
  • A reference is, basically, a pointer? There's extra work to dereference the pointer? – Sam Varshavchik Aug 03 '22 at 18:05
  • @0x5453 O0 optimization – mmcblk1 Aug 03 '22 at 18:06
  • 4
    General rule of thumb - pass primitive types by value and everything else by reference. When the value fits into a single register, it is more efficient than having to 'lookup' what was the value in memory at the address pointed by the reference. (The way I see it, a reference is just a pointer to some memory address but can't be null.) – AlexG Aug 03 '22 at 18:14
  • @AlexG Seems true. Used a `void foo(const int* const n)` prototype and it behaves exactly like a reference. – mmcblk1 Aug 03 '22 at 18:17
  • 4
    `ldr r3, [r3]` is _dereferencing_ your `const int &n`. `r3` holds the `int` in your first function, but holds the memory location of the `int` in your second function, so there's an extra step to get the actual value, since it was not passed to the function. – Drew Dormann Aug 03 '22 at 18:17
  • 1
    I'm surprised I can't find a good previous answer too this. There are generic computing answers but I was expecting something C++. Anyone? – Persixty Aug 03 '22 at 18:34
  • With rare exceptions lvalue references to const should usually be replaced with passing the value by value. The only scenario I can think of where a lvalue reference to a const primitive could be necessary would be if the address is relevant... – fabian Aug 03 '22 at 18:46
  • 1
    As a slight aside, declaring a parameter passed by value as `const` doesn't really achieve anything. In fact, I'd call it an anti-pattern. – Paul Sanders Aug 03 '22 at 19:29
  • @PaulSanders Well, in my case, MISRA guidelines forces me to do so – mmcblk1 Aug 03 '22 at 19:30
  • @mmcblk1 How silly. – Paul Sanders Aug 03 '22 at 19:31

1 Answers1

2

A reference can be understood as something in between a name alias and a pointer. From What are the differences between a pointer variable and a reference variable?:

A compiler keeps "references" to variables, associating a name with a memory address. Its job is to translate any variable name to a memory address when compiling. When you create a reference, you only tell the compiler that you assign another name to the pointer variable; that's why references cannot "point to null". A variable cannot be, and not be at the same time. Pointers are variables; they contain the address of some other variable, or can be null. The important thing is that a pointer has a value, while a reference only has a variable that it is referencing.

  • On a 32 bit-machine, a pointer has 4 bytes (4*8 = 32 bits) while on a 64-bit machine a pointer has 8 bytes (8*8 = 64 bits), because that is the size of a single memory address.

  • On a 32 bit-machine, int, long are each 4 bytes (32 bits) quantities. On most 64-bit systems, long becomes 8 bytes (64 bits), but int remains 32-bit (4 bytes).

[Speaking about primitive types, the size of char is fixed at 1 byte (or CHAR_BIT = 8 bits) explicitly in the C++ standard.]

Given that passing a const reference as function parameter requires the compiler to dig for the memory address of the referenced variable, in case the variable is a primitive type such as int or char the digging in memory (pointer is 8 bytes) is going to be more expensive than passing the variable itself by value (4 bytes for int, 1 byte for char).

The question is about the efficiency of parameters passed by const reference. Of course, a function accepting a parameter by non-const reference would have the advantage - unrelated to efficiency - of allowing modifications of the referenced variable to persist, when control goes back to the caller.

Giogre
  • 1,444
  • 7
  • 19
  • 2
    "*On 64-bit systems, `long` becomes 8 bytes (64 bits)*" - that depends on platform and even compiler. For instance, Windows defines `DWORD` and `LONG` as 32bit even on a 64-bit system, but they are both defined in terms of `(unsigned) long`, which means `long` should always be 32bit. However, I've seen it go both ways, where some compilers treat `long` as 32bit on a 64bit system, and some compilers treat `long` as 64bit on a 64bit system. The C++ standard does not dictate what exact size `long` must be (same with all other integer types), so it is *implementation-defined*. – Remy Lebeau Aug 03 '22 at 19:35
  • @RemyLebeau I'll modify "On most platforms ..." – Giogre Aug 03 '22 at 19:37
  • @RemyLebeau Specifically, >= `int`, I believe. – Paul Sanders Aug 03 '22 at 20:26
  • @PaulSanders Well, yes, the standard dictates `sizeof(long) >= sizeof(int)`, but it doesn't dictate exact sizes for `sizeof(int)` and `sizeof(long)`. – Remy Lebeau Aug 03 '22 at 23:24