指针用途与使用限制
# 3. 指针用途与使用限制
Go 是带有垃圾回收的编程语言,指针在 Go 中依旧位于 C 位,它的作用不仅体现在语法层面上,更体现在 Go 运行时层面,尤其是内存管理与垃圾回收这两个地方,这两个运行时机制只关心指针。
# 3.1 指针的用途:
- 存在的意义就是为了使**“可改变”。在 Go 中,使用 *T 类型的变量调用方法、以 *T 类型作为函数或方法的形式参数、返回 *T 类型的返回值等的目的,都是因为指针可以改变其指向的内存单元的值。**
- 指针传递的开销是常数级的(在 x86-64 平台上仅仅是 8 字节的拷贝),可控可预测。无论指针指向的是一个字节大小的变量,还是一个拥有 10000 个元素的[10000]int 型数组,传递指针的开销都是一样的。
# 3.2 指针的使用限制
限制一:限制了显式指针类型转换。
在Go中,显式指针转换会得到 Go 编译器的报错信息:
package main
import (
"fmt"
"unsafe"
)
func main() {
var a int = 0x12345678
var pa *int = &a
var pb *byte = (*byte)(pa) // 编译器报错:cannot convert pa (variable of type *int) to type *byte
fmt.Printf("%x\n", *pb)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
如果非要进行这个转换,Go 也提供了 unsafe 的方式,需要使用到 unsafe.Pointer,如下面代码:
func main() {
var a int = 0x12345678
var pa *int = &a
var pb *byte = (*byte)(unsafe.Pointer(pa)) // ok
fmt.Printf("%x\n", *pb) // 78
}
1
2
3
4
5
6
2
3
4
5
6
如果使用 unsafe 包中类型或函数,代码的安全性就要由开发人员自己保证,也就是开发人员得明确知道自己在做啥!
限制二:不支持指针运算。
指针运算是安全问题的“滋生地”,为了安全性,Go 在语法层面抛弃了指针运算这个特性。在 Go 语言中,下面的代码将得到 Go 编译器的报错信息:
package main
func main() {
var arr = [5]int{1, 2, 3, 4, 5}
var p *int = &arr[0]
println(*p)
p = p + 1 // 编译器报错:cannot convert 1 (untyped int constant) to *int
println(*p)
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
如果非要做指针运算,Go 依然提供了 unsafe 的途径,比如下面通过 unsafe 遍历数组的代码:
package main
import "unsafe"
func main() {
var arr = [5]int{11, 12, 13, 14, 15}
var p *int = &arr[0]
var i uintptr
for i = 0; i < uintptr(len(arr)); i++ {
p1 := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + i*unsafe.Sizeof(*p)))
println(*p1)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
通过 unsafe.Pointer 与 uintptr 的相互转换,间接实现了“指针运算”。
即便可以使用 unsafe 方法实现“指针运算”,Go 编译器也不会为开发人员提供任何帮助,开发人员需要自己告诉编译器要加减的绝对地址偏移值,而不是像 C 语言中那样,可以根据指针类型决定指针运算中数值 1 所代表的实际地址偏移值。
上次更新: 2022/06/12, 15:48:09