f(int (&[N])
一样将数组的引用作为参数传递。是的,这是由ISO标准保证的语法,但我很好奇编译器是如何处理的。我在这个线程中找到了相关信息,但不幸的是,它没有回答我的问题——编译器如何实现这种语法?然后我写了一个演示程序,希望从汇编语言中看到一些东西:
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;
}
最初,我猜测它仍然会转化为指针,但会通过寄存器隐式传递长度,然后在函数体中再次转换为数组。但汇编代码告诉我这不是真的。
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
我承认我不熟悉汇编语言,但很明显,这三个函数的汇编代码是相同的!所以,在汇编器代码之前一定发生了什么。无论如何,与数组不同,指针不知道长度,对吧?
问题:
- 编译器是如何工作的?
- 现在标准允许通过引用传递数组,这是否意味着实现起来很简单?如果是这样,为什么不允许按值传递?
对于问题2,我的猜测是因为早期的C ++和C代码复杂性。毕竟,在函数参数中,int []
等同于int *
已经成为了一种传统。也许一百年后,它会被淘汰?
length == 3
,那么foo_r
和foo_t
实际上是相同的。而且长度不需要传递,因为编译器会在编译时内部处理。 - Some programmer dude-O3 -fno-inline-functions
编译,或者使用__attribute__((noinline))
的-O3
,来消除-O0
带来的噪音。嗯,我应该在如何从GCC/clang汇编输出中移除“噪音”?的答案中更新这些建议。 - Peter Cordes__attribute__((noinline))
而不仅仅是-O3
。如果没有它,你还需要-fno-inline-small-functions
。请参见我的答案中的更新。 - Peter Cordes