正如我们所知,在 C++ 中,我们可以像 f(int (&[N]) 一样将数组的引用作为参数传递。是的,它是由 iso 标准保证的语法,但我很好奇编译器在这里是如何工作的。我找到了这个 thread ,但不幸的是,这并没有回答我的问题——编译器是如何实现这个语法的?
然后我写了一个demo,希望能从汇编语言中看到一些东西:
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
live demo int[] 在函数参数中等于 int* 一直是传统。也许一百年后,它会被弃用?
最佳答案
在汇编语言中,对数组的 C++ 引用与指向第一个元素的指针相同。
即使是 C99 int foo(int arr[static 3]) 仍然只是 asm 中的一个指针。 static syntax 向编译器保证即使 C 抽象机不访问某些元素,它也可以安全地读取所有 3 个元素,因此例如它可以对 cmov 使用无分支 if 。
调用者不会在寄存器中传递长度,因为它是编译时常量,因此在运行时不需要。
您可以按值传递数组,但前提是它们位于结构体或 union 体中。在这种情况下,不同的调用约定有不同的规则。 What kind of C11 data type is an array according to the AMD64 ABI 。
您几乎从不想按值传递数组,因此 C 没有语法是有道理的,而 C++ 也从未发明任何语法。通过常量引用(即 const int *arr )传递效率更高;只是一个指针 arg。
通过启用优化来消除编译器噪音:
我将您的代码放在 Godbolt 编译器资源管理器中,使用 gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions 进行编译以阻止它内联函数调用。这消除了 -O0 调试构建和帧指针样板的所有噪音。 (我只是在手册页中搜索了 inline 并禁用了内联选项,直到我得到了我想要的。)
您可以在函数定义上使用 GNU C -fno-inline-small-functions 来禁用特定函数的内联,而不是 __attribute__((noinline)) 等,即使它们是 static 。
我还添加了对没有定义的函数的调用,因此编译器需要在内存中具有具有正确值的 arr[],并在其中两个函数中为 arr[4] 添加了一个存储。这让我们测试编译器是否警告超出数组边界。
__attribute__((noinline, noclone))
void foo_p(int*arr) {(void)arr;}
void foo_r(int(&arr)[3]) {arr[4] = 41;}
template<int length>
void foo_t(int(&arr)[length]) {arr[4] = 42;}
void usearg(int*); // stop main from optimizing away arr[] if foo_... inline
int main()
{
int arr[] = {1, 2, 3};
foo_p(arr);
foo_r(arr);
foo_t(arr);
usearg(arr);
return 0;
}
-Wall -Wextra without function inlining, on Godbolt :由于我从您的代码中消除了未使用的参数警告,我们得到的唯一警告来自模板,而不是来自 foo_r :<source>: In function 'int main()':
<source>:14:10: warning: array subscript is above array bounds [-Warray-bounds]
foo_t(arr);
~~~~~^~~~~
void foo_t<3>(int (&) [3]) [clone .isra.0]:
mov DWORD PTR [rdi], 42 # *ISRA.3_4(D),
ret
foo_p(int*):
rep ret
foo_r(int (&) [3]):
mov DWORD PTR [rdi+16], 41 # *arr_2(D),
ret
main:
sub rsp, 24 # reserve space for the array and align the stack for calls
movabs rax, 8589934593 # this is 0x200000001: the first 2 elems
lea rdi, [rsp+4]
mov QWORD PTR [rsp+4], rax # MEM[(int *)&arr], first 2 elements
mov DWORD PTR [rsp+12], 3 # MEM[(int *)&arr + 8B], 3rd element as an imm32
call foo_r(int (&) [3])
lea rdi, [rsp+20]
call void foo_t<3>(int (&) [3]) [clone .isra.0] #
lea rdi, [rsp+4] # tmp97,
call usearg(int*) #
xor eax, eax #
add rsp, 24 #,
ret
foo_p() 的调用仍然被优化掉了,可能是因为它没有做任何事情。 (我没有禁用过程间优化,甚至 noinline 和 noclone 属性也没有阻止。)将 *arr=0; 添加到函数体会导致从 main 调用它(在 rdi 中传递一个指针,就像其他 2 )。clone .isra.0 注释:gcc 对函数进行了定义,该函数采用指向 arr[4] 而不是基本元素的指针。这就是为什么有一个 lea rdi, [rsp+20] 来设置 arg,以及为什么商店使用 [rdi] 来取消引用而不发生位移的点。 __attribute__((noclone)) 会阻止它。disp8),但在其他情况下可能很有用。调用者需要知道它是函数修改版本的定义,比如 void foo_clone(int *p) { *p = 42; } ,这就是为什么它需要在损坏的符号名称中对其进行编码。main 推导模板有关?main 使用 mov rdi, rsp 而不是 lea rdi, [rsp+4] 。即,将 &arr[-1] 作为函数 arg,因此克隆将使用 mov dword ptr [rdi+20], 42 。main 之类的调用者有用,它们在 rsp 之上分配了一个数组 4 个字节,我认为 gcc 只是在寻找使函数本身更高效的 IPO,而不是某个特定调用者中的调用序列。
关于c++ - 从编译器的角度来看,如何处理数组的引用,以及为什么不允许按值传递(而不是衰减)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50775127/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m
我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or
我正在学习如何在我的Ruby代码中使用Module.prepend而不是alias_method_chain,我注意到有些人使用send调用它(example):ActionView::TemplateRenderer.send(:prepend,ActionViewTemplateRendererWithCurrentTemplate)而其他人直接调用它(example):ActionView::TemplateRenderer.prepend(ActionViewTemplateRendererWithCurrentTemplate)而且,虽然我还没有看到任何人使用这种风格,但我从
我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试