[译] Go Frequently Asked Questions(FAQ) - Pointers and Allocation | golang官方文档中文翻译之指针和内存分配

本文同步发布于: [译] Go Frequently Asked Questions(FAQ) - Pointers and Allocation | golang官方文档中文翻译之指针和内存分配 | yoko blog

前言

本篇译文对应的原文
标题:Pointers and Allocation - Go Frequently Asked Questions(FAQ)
作者:Go官方文档
地址:https://golang.org/doc/faq#Pointers

本文标明yoko备注的内容是我自己写的备注,其余的都是对英文原文的翻译。

目录

  • 函数参数什么时候按值传递?
  • 方法定义在值类型上还是指针类型上?
  • new和make有什么区别?
  • int类型在64位系统上大小是多少?
  • 我如何知道一个变量是分配在堆上还是栈上?
  • 为什么我的golang进程占用了很多虚拟内存?
  • TODO
    • 什么时候需要使用指向interface的指针?

函数参数什么时候按值传递?

和所有的c语言家族的语言一样,golang中的所有东西都是按值传递。也就是说,函数总是获取到所传递实参的拷贝,就像有赋值语句把值传递给参数。比如说,传递一个int值给一个函数会拷贝这个int值,传递一个指针值也给拷贝这个指针,但不是拷贝指针所指向的数据。

map和slice类型的行为和指针类似:它们是包含了指向底层map和slice数据的描述符。拷贝map或slice时并不会拷贝它指向的数据。拷贝一个interface会拷贝interface存储的数据。如果interface存储的是一个结构体,拷贝这个interface会拷贝这个结构体。如果interface存储的是一个指针。拷贝这个interface会拷贝这个指针,但是同样的,不会拷贝它指向的数据。

方法定义在值类型上还是指针类型上?

func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct)  valueMethod()   { } // method on value

对于不熟悉指针的开发者,对这两个例子的区别可能有些困惑,但是实际情况其实十分简单。当在一个类型上定义一个方法时,接收者(上面例子中的s)的行为完全和将这个接收者作为这个方法的参数是一样的。考虑把接收者定义为值类型或指针类型,和把函数的参数定义为值类型或指针类型是一样的。有以下几点需要考虑。

首先,也是最重要的,方法是否需要修改接收者?如果需要,那么接收者必须为指针类型(slice和map表现得和引用一样,所以它们有些特殊,但是比如说在方法中改变slice的长度仍然需要指针类型)。在上面的例子,如果pointerMethod改变了s中元素的值,调用者会看到这变化,但是valueMethod是被调用者参数的一份拷贝所调用的(这就是按值传递的定义),所以改变s中元素的值对调用者是不可见的。

随便说一句,java中方法的接收者总是使用指针类型,虽然它们的指针本质上有些伪装(有一个提案是往java中添加值类型接收者)。golang中的值类型接收者是少见的。

第二点是考虑效率。如果接收者比较大,比如说一个大结构体,使用指针类型接收者代价会更小些。

接下来考虑一致性。如果有的方法的接收者类型必须为指针类型,那么其余方法也应该使用指针类型,使得方法集合是一致的,无论实际使用的调用类型是值类型还是指针类型。

对于基础类型,slice,小结构体这些类型,值类型接收者的代价是很低的,所以除非方法从语义上需要指针类型接收者,那么使用值类型接收者是高效且清晰的。

yoko备注
英文原文在说明一致性,方法集合时,引用FAQ中另外一个章节的内容:
https://golang.org/doc/faq#different_method_sets
我个人的理解是,
当方法定义为值类型接收者时,那么只能使用值类型调用
当方法定义为指针类型接收者时,那么不但能使用指针类型调用,还能使用值类型调用

如果已经有方法定义为指针类型接收者了,那么为了保持一致,其余的方法应该都定义为指针类型接收者
不然会造成指针类型的对象有的方法能调,有的不行

并且指针类型可以在方法内解引用得到实体对象
但是值类型由于要保持方法内修改对象对外不可见,那么在方法内获取对象的地址是不安全的

new和make有什么区别?

简单来说:new分配内存,而make用来初始化slice,map,channel这三个类型。

更详细的细节参见:https://golang.org/doc/effective_go.html#allocation_new

int类型在64位系统上大小是多少?

int和uint的大小是具体实现相关的,但是在一个指定的平台上它们是相等。为了可移植性,依赖特定大小的值的代码应该使用显式大小的类型,比如int64。在32位系统编译器默认使用32位整型,在64位系统整型使用64位(从历史来看,也不总是这样)。

另一方面,浮点类型总是特定大小固定的(没有浮点基础类型),因为开发者在使用浮点类型的数值时应该知道精度。默认类型用来表示应该一个未指定类型的浮点常量是float64。所以foo := 3.0声明了一个类型为float64的变量。对于一个float32类型的变量如果使用一个未指定类型的常量来初始化的话,变量的类型必须在变量声明时显式指定:

var foo float32 = 3.0

或者,常量必须使用类型转换就像这样 foo := float32(3.0)

我如何知道一个变量是分配在堆上还是栈上?

从正确性角度来说,你不需要知道一个变量是分配在堆上还是栈上。golang中的每个变量,只要还有引用,生命周期就会一直持续。golang选择变量存储在何位置的具体实现是不影响golang的语言语义的。

变量存储位置对编写高效程序是有影响的。如果可能的话,golang编译器会将函数的局部变量分配在函数的栈空间上。然后,如果编译器不能证明这个变量在函数返回后不会被引用,那么编译器必须将这个变量分配在带垃圾回收机制的堆上以避免出现野指针错误。并且,如果一个局部变量特别大,把它分配在堆上可能会比分配在栈上更好些。

在当前的编译器中,如果一个变量的地址被获取,那么这个变量可能会分配在堆上。然而,一个基础的逃逸分析可以识别出一些变量在函数返回后不再存活的场景,那么这种变量会继续保存在栈上。

为什么我的golang进程占用了很多虚拟内存?

golang的内存分配器预申请了一大块虚拟内存区域用于后续内存申请。这个虚拟内存是和具体的golang进程关联的;这个预分配行为不会剥夺其它进程的内存。

如果想知道一个golang进程的实际内存申请大小,使用Unix的top命令并观察RES(linux平台)或者RSIZE(macos平台)。

TODO

什么时候需要使用指向interface的指针?

yoko备注
什么时候需要使用指向interface的指针?

这一小节翻译的不好,建议阅读英文原文。
对本小节的临时翻译我也贴在本文末尾处了。

针对这个问题的答案是,不要使用指向interface的指针。

个人感觉它所表达的内容有两点:
第一点是,一个实际类型只要实现了一个interface的接口,
那么这个实际类型的值或者这个实际类型的指针都能够被这个interface接收。

第二点是,不要使用指向interface的指针(注意,并不是第一点中说的指向实际类型的指针,要区分开),
interface并不能接收指向该interface的指针,编译会报错。

几乎永远不要使用。指向interface的指针只出现在罕见的情况,它掩盖了interface值的类型用于延时取值。

一个常见的错误是传递一个指向interface值的指针给一个需要interface参数的函数。编译器会对此报错,但是这种情况仍然让人困惑,因为有时候指针需要满足interface。尽管一个指向具体类型的指针可以满足interface,但是有一种例外是一个指向interface的指针无法满足一个interface。

考虑以下变量声明

var w io.Writer

打印函数fmt.Fprintf把满足io.Writer接口的值(也就是实现了Write接口方法)作为第一个参数。所以我们可以像下面这样写

fmt.Fprintf(w, "hello, world\n")

然而如果我们传递w的地址,程序将编译失败。

fmt.Fprintf(&w, "hello, world\n") // Compile-time error.

一种例外情况是,任何值,即使是一个指向interface的指针,可以被赋值为空interface类型(interface{})。即使如此,指向interface的指针几乎肯定是一种错误;结果会令人困惑。

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

推荐阅读更多精彩内容