在学习go语言时,对于数组的部分有一点困惑,为什么赋值和传参要复制整个数组,这种设计相对于传统方式有什么好处么?
在学习go语言时,对于数组的部分有一点困惑,为什么赋值和传参要复制整个数组,这种设计相对于传统方式有什么好处么?
题主的问题:数组fixed-size-array,在go博客http://blog.golang.org/slices里也有一些提及,我自己的观点是:
因为array的长度就是array类型的一部分,go里的array从生产力角度确实应用空间很有限,设计者就是想要我们多用slice吧,如果想灵活些就把底下的内存操作交给slice,而slice底下是通过操作array来帮你实现,注重效率就自己用array。
另外我觉得go的作者们是资深c粉,go目标也是替代google的c++ base的庞大代码库的系统语言。c中栈上数组都是要指定大小的跟go类似,堆上申请内存malloc也当然是要指定大小的,用数组的指针时候也都要手动去注意考虑数组的长度。像为处理c的数组做好接口,也都要啰嗦的多传一个参数指定长度,要么就声明可能会覆盖而把责任抛给程序员,POSIX的也概莫能外,从此有太多太多bug因此而起,所以这个array的设计是很自然的对c的一种改进。
上篇博客对append的详细解释就是应对了一些很容易犯错的地方。slice的具体解释golang blog上有一篇文章:http://blog.golang.org/go-slices-usage-and-internals
2 回答1.3k 阅读
2 回答1.1k 阅读
2 回答1.1k 阅读
2 回答859 阅读
3 回答677 阅读
999 阅读
1 回答780 阅读
这个要说明白可能得很长的文章,所以,这里就简单点来说吧,关于数组:
不像c语言中的数组变量,在golang中,数组变量是值,比如把一个数组传递给一个函数,它传递的是原来数组的拷贝,而不是像c语言中那样传递的是一个指向原来数组的指针,这会导致把大型数组传递给函数的效率比较低,所以官方教程建议我们在编程中更多的使用slice,这个slice更像c中的数组,不过在运行时可以检查是否越界访问,比c中的数组更安全。本文就来分析下golang中slice的实现。我们以一个简单的程序来开始分析:
程序输出
slice s
有两个元素2
和3
,长度为2
,容量(capacity)
为4
用
gdb
把断点设置在12行s := f(i, 1, 3)
, 然后运行到断点并反汇编附近代码(我加上了行号):7,8两行把传递给f()函数的后两个参数1和3放入堆栈, 1-6行把第一个参数i放入堆栈,可以看到slice变量i共占了12个字节, 那我们看看这12个字节里面的内容到底是啥。把断点设置在0x08048cb6:
看到esp的值为
0x151f90
, 这个地方就是变量i的拷贝,看看:从官方教程我们可以知道slice有一个lengh和capacity,再结合上面的go代码可以知道后面两个0x00000005一定lengh和capacity。再看看0x98029c00这个数字,看起来很像一个地址,所以我们看看这个地址开始的几个内存单元中存放的是啥
这不就是数组[1,2,3,4,5]吗,所以到现在我们可以确定一个slice在内存中由4部分组成:
这样我们可以用c语言的struct来表示这个slice:
ptr
一定是第一个成员,len
和cap
的顺序还不敢肯定,后面的分析我们可以确认他们的顺序从上面的分析还可以得出结论:
slice
作为参数传递时只传递了指针,长度和容量,而数组却没有copy.为了验证上面的分析,我们继续反汇编函数f():
为了便于叙述,我把
gdb dump
出来的汇编代码前面加上了行号。1 - 7
行是函数序言,它是由链接器8l插入的,它跟slice的实现没有关系,这里跳过,以后我们再来专门讨论这个;第8行
sub $0xc,%esp
调整堆栈寄存器esp的值,分配了12个字节的栈空间,这12个字节中包含了局部变量 r 所占的栈空间。这条指令执行完成后,栈布局是这样的:继续分析上面的指令:
这几行检查是否满足条件
y <= s.cap && x <= y && x >=0 && y >=0
(注意这里用的是无符号比较转移指令jbe和ja),如果不满足则说明越界或不合法的slice操作,需要调用runtime.panicslice()结束程序,否则继续执行后面的指令;下面这几条指令比较直白,我把对应的c代码直接写在汇编指令后面
用c代码来表示上面这几行汇编指令大概就是这个样子 :
从上面的分析可以看到slice操作只有9-24这16条指令,所以说是相当迅速的,这也验证了官方文档说的slice is cheap!