As we know, in C++, we can pass an array's reference as an argument like f(int (&[N])
. Yes, it is syntax guaranteed by the iso standard, but I am curious about how the compiler works here. I found this thread, but unfortunately, this doesn't answer my question -- How is this syntax implemented by the compiler?
I then wrote a demo and hoped to see something from the assembly language:
void foo_p(int*arr) {}
void foo_r(int(&arr)[3]) {}
template<int length>
void foo_t(int(&arr)[length]) {}
int main(int argc, char** argv)
{
int arr[] = {1, 2, 3};
foo_p(arr);
foo_r(arr);
foo_t(arr);
return 0;
}
Originally, I guess it will still decay to the pointer, but will pass length implicitly via a register, then turn back into an array in the function body. But the assembly code tells me this is not true
void foo_t<3>(int (&) [3]):
push rbp #4.31
mov rbp, rsp #4.31
sub rsp, 16 #4.31
mov QWORD PTR [-16+rbp], rdi #4.31
leave #4.32
ret #4.32
foo_p(int*):
push rbp #1.21
mov rbp, rsp #1.21
sub rsp, 16 #1.21
mov QWORD PTR [-16+rbp], rdi #1.21
leave #1.22
ret #1.22
foo_r(int (&) [3]):
push rbp #2.26
mov rbp, rsp #2.26
sub rsp, 16 #2.26
mov QWORD PTR [-16+rbp], rdi #2.26
leave #2.27
ret #2.27
main:
push rbp #6.1
mov rbp, rsp #6.1
sub rsp, 32 #6.1
mov DWORD PTR [-16+rbp], edi #6.1
mov QWORD PTR [-8+rbp], rsi #6.1
lea rax, QWORD PTR [-32+rbp] #7.15
mov DWORD PTR [rax], 1 #7.15
lea rax, QWORD PTR [-32+rbp] #7.15
add rax, 4 #7.15
mov DWORD PTR [rax], 2 #7.15
lea rax, QWORD PTR [-32+rbp] #7.15
add rax, 8 #7.15
mov DWORD PTR [rax], 3 #7.15
lea rax, QWORD PTR [-32+rbp] #8.5
mov rdi, rax #8.5
call foo_p(int*) #8.5
lea rax, QWORD PTR [-32+rbp] #9.5
mov rdi, rax #9.5
call foo_r(int (&) [3]) #9.5
lea rax, QWORD PTR [-32+rbp] #10.5
mov rdi, rax #10.5
call void foo_t<3>(int (&) [3]) #10.5
mov eax, 0 #11.11
leave #11.11
ret #11.11
I admit that I am not familiar with the assembly language, but clearly, the three function's assembly codes are the same! So, something must happen before the assembler codes. Anyway, unlike the array, the pointer knows nothing about the length, right?
Questions:
- how does the compiler work here?
- Now that the standard allows to pass an array by reference, does that mean it is trivial to implement? If so, why isn't passing by value allowed?
For Q2, my guess is for the complexity of the former C++ and C codes. After all, int[]
being equal to int*
in function parameters has been a tradition. Maybe one hundred years later, it will be deprecated?
foo_r
andfoo_t
are really the same iflength == 3
. And no the length doesn't need to be passed, as it's handled internally by the compiler at compilation-time. – Some programmer dude-O3 -fno-inline-functions
, or-O3
with__attribute__((noinline))
to remove the noise of-O0
. Hmm, I should probably update my answer on How to remove "noise" from GCC/clang assembly output? with those suggestions. – Peter Cordes-O3
. Without that, it turns out you also need-fno-inline-small-functions
. See updates in my answer. – Peter Cordes