go语言函数如何传递数组变量

这篇文章以实践的方式验证go语言函数之间是如何传递数组类型变量的。
和slice相比,go对于array传参是传递整个array内容的,而不是引用,即把原array内容做一个拷贝,然后把拷贝后的内容值作为参数给被调用者使用。

以如下go语言程序为例子:

package main

const SIZE = 16

func main() {
  var ss [SIZE]int64

  ss[0]      = 0x1111
  ss[SIZE-1] = 0x2222

  useArray(ss)
}

func useArray(ss [SIZE]int64) {
  ss[0]      = 0x3333
  ss[SIZE-1] = 0x4444
}

逐段分析生成的哦汇编代码,先看数组是如何定义的:

  var ss [SIZE]int64
  44d682:   48 8d bc 24 80 00 00    lea    0x80(%rsp),%rdi
  44d689:   00
  44d68a:   0f 57 c0                xorps  %xmm0,%xmm0
  44d68d:   48 89 6c 24 f0          mov    %rbp,-0x10(%rsp)
  44d692:   48 8d 6c 24 f0          lea    -0x10(%rsp),%rbp
  44d697:   e8 ee ab ff ff          callq  44828a <runtime.duffzero+0x10a>
  44d69c:   48 8b 6d 00             mov    0x0(%rbp),%rbp

在main函数里面变量ss被分配在栈中,位置是用%rsp+0x80开始的 16*8字节,所以

  &ss[0] = %rsp+0x80
  &ss[1] = %rsp+0x88
  ...
  &ss[15] = %rsp+0xf8

因为我们看两条赋值语句的代码:
ss[0]对应的是%rsp + 0x80
ss[15]对应的是%rsp + 0xf8

  ss[0]      = 0x1111
  44d6a0:   48 c7 84 24 80 00 00    movq   $0x1111,0x80(%rsp)
  44d6a7:   00 11 11 00 00
  ss[SIZE-1] = 0x2222
  44d6ac:   48 c7 84 24 f8 00 00    movq   $0x2222,0xf8(%rsp)
  44d6b3:   00 22 22 00 00

同时我们也能看到数组被定义的同时,也进行了初始化操作,调用runtime.duffzero函数,duffzero函数接收参数%rdi,把从%rdi地址开始的内存空间(在这个例子中就是数组ss的首地址)填满%xmm0的值,这个函数的设计很巧妙,后面我们介绍它。

再看函数useArray的调用语句

  useArray(ss)
  44d6b8:   48 89 e7                mov    %rsp,%rdi
  44d6bb:   48 8d b4 24 80 00 00    lea    0x80(%rsp),%rsi
  44d6c2:   00
  44d6c3:   48 89 6c 24 f0          mov    %rbp,-0x10(%rsp)
  44d6c8:   48 8d 6c 24 f0          lea    -0x10(%rsp),%rbp
  44d6cd:   e8 fe ae ff ff          callq  4485d0 <runtime.duffcopy+0x310>
  44d6d2:   48 8b 6d 00             mov    0x0(%rbp),%rbp
  44d6d6:   e8 25 00 00 00          callq  44d700 <main.useArray>

从这段代码可以看到,main函数把ss的内容做了一个完整拷贝,函数runtime.duffcopy用来拷贝内存从%rsi(即ss的首地址)拷贝到%rdi(即当前栈顶),细心的读者会发现这个函数和前面的runtime.duffzero函数一样有一个问题,即没有指定内存的大小,不知道该拷贝填充多大的内存,虽然指定了内存地址的开始地址,但是没有指定结束地址。这其实就是这两个函数设计的巧妙之处,后面我们再介绍。

接着再看函数useArray的实现语句

func useArray(ss [SIZE]int64) {
  ss[0]      = 0x3333
  44d700:   48 c7 44 24 08 33 33    movq   $0x3333,0x8(%rsp)
  44d707:   00 00
  ss[SIZE-1] = 0x4444
  44d709:   48 c7 84 24 80 00 00    movq   $0x4444,0x80(%rsp)
  44d710:   00 44 44 00 00
}

在函数useArray内部参数ss占用的空间是%rsp+0x8到%rsp+0x80的范围,注意此%rsp和main函数的%rsp相差一个8字节位置,因为main函数调用到useArray函数时,%rsp指针发生了移动(保存了%rsp)。

我们看到在调用useArray前后栈的变化

in main             values                      in useArray
------------------+--------------------------|-------------------------
%rsp + 0xf8 -->   | ss[15]                   |
%rsp + 0xf0 -->   | ss[14]                   |
...               |                          |
%rsp + 0x88 -->   | ss[1]                    |
%rsp + 0x80 -->   | ss[0]                    |
%rsp + 0x78 -->   | reserved.param.ss[15]    |    <-- %rsp + 0x80
%rsp + 0x70 -->   | reserved.param.ss[14]    |    <-- %rsp + 0x78
...               |                          |
%rsp + 0x8  -->   | reserved.param.ss[1]     |    <-- %rsp + 0x10
%rsp + 0x0  -->   | reserved.param.ss[0]     |    <-- %rsp + 0x8
                  | (useArray return addr)   |    <-- %rsp + 0x0

最后我们来看下runtime.duffzero和runtime.duffcopy
这两个系统函数由go运行环境提供,功能类似于memset和memory,它是由汇编语言直接生成;代码如下

0000000000448180 <runtime.duffzero>:
  448180:   0f 11 07                movups %xmm0,(%rdi)
  448183:   0f 11 47 10             movups %xmm0,0x10(%rdi)
  448187:   0f 11 47 20             movups %xmm0,0x20(%rdi)
  44818b:   0f 11 47 30             movups %xmm0,0x30(%rdi)
  44818f:   48 83 c7 40             add    $0x40,%rdi

  448193:   0f 11 07                movups %xmm0,(%rdi)
  448196:   0f 11 47 10             movups %xmm0,0x10(%rdi)
  44819a:   0f 11 47 20             movups %xmm0,0x20(%rdi)
  44819e:   0f 11 47 30             movups %xmm0,0x30(%rdi)
  4481a2:   48 83 c7 40             add    $0x40,%rdi

  ...

  4482b0:   c3                      retq


00000000004482c0 <runtime.duffcopy>:
  4482c0:   0f 10 06                movups (%rsi),%xmm0
  4482c3:   48 83 c6 10             add    $0x10,%rsi
  4482c7:   0f 11 07                movups %xmm0,(%rdi)
  4482ca:   48 83 c7 10             add    $0x10,%rdi


  4482ce:   0f 10 06                movups (%rsi),%xmm0
  4482d1:   48 83 c6 10             add    $0x10,%rsi
  4482d5:   0f 11 07                movups %xmm0,(%rdi)
  4482d8:   48 83 c7 10             add    $0x10,%rdi

  ...
  
  448640:   c3                      retq  

可以看到这两个函数都非常整齐,就是由4条/5条指令组的重复,删除了所有的函数entry/exit的标准模板代码,然后在最后有一个RET指令。
每一条指令组使用%xmm0寄存器来一次拷贝/赋值16字节的内容,每一组由4条指令来拷贝/赋值64字节内容。

前面提到的runtime.duffzero和runtime.duffcopy函数调用缺少参数指定内存大小的问题怎么解决吗?在这里不由callee不负责,而是由caller负责,caller根据要操作内存的大小来决定call到这两个函数的具体哪一条指令,对比通常的函数调用都是跳转到函数的入口地址,而对这个函数是跳转到函数内部的某一条指令,直到运行到RET指令为止,这期间运行了多少条拷贝赋值指令,就是操作了多大内存空间。

  44d697:   e8 ee ab ff ff          callq  44828a <runtime.duffzero+0x10a>
...
  44d6cd:   e8 fe ae ff ff          callq  4485d0 <runtime.duffcopy+0x310>

看前面的代码,通常的函数调用都是

callq  0x...... <functionmame>

也就是直接调用函数的入口指令,而这两个函数都是带一个偏移量的,这也就只有像runtime.duffzero和runtime.duffcopy这种内部逻辑简单的函数可以这么调用。这实际上编译器处理了大量的工作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,077评论 25 707
  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,607评论 1 19
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,139评论 30 470
  • 王爽汇编全书知识点大纲 第一章 基础知识 机器语言 汇编语言的产生 汇编语言的组成 存储器 cpu对存储器的读写 ...
    2c3ba901516f阅读 2,417评论 0 1
  • 撒旦说:“去捕获吧!去迷惑吧!去把他们都扯入地狱吧!” 于是,它们就出来了。 它们是魔鬼,是撒旦的子民,从黑暗的地...
    鹤洺阅读 702评论 4 14