1. Go如何保证不被引用的变量不被垃圾回收?(得物)
Go 的编译器/GC 可能会在最后一次使用之后就把对象判定为不可达,即使在作用域还没结束。
这种情况在涉及 finalizer、cgo 调用 或 syscall 时比较常见。
为避免这种「被过早回收」的问题,Go 提供了:
import "runtime"
func foo() {
obj := make([]byte, 1024)
use(obj) // 逻辑上这里还没结束
// 告诉 GC:在这行之前 obj 绝对不会被回收
runtime.KeepAlive(obj)
}
runtime.KeepAlive(x) 的语义是:直到执行到这一行时,x 才被认为不再存活。
这在 cgo 中尤为重要,比如你把 Go 的内存指针传到 C 代码,C 可能异步操作它,如果没加 KeepAlive,Go 提前回收就会崩溃。
2. interface 底层
在 Go 的运行时(runtime)里,接口分为两类:
-
空接口
interface{}
底层表示是:type eface struct { _type *_type // 动态类型信息 data unsafe.Pointer // 指向实际数据 } -
非空接口(有方法的接口)
底层表示是:type iface struct { tab *itab // 类型方法表,记录动态类型和接口方法实现的对应关系 data unsafe.Pointer // 指向实际数据 }
所以 接口变量本质上是一个二元组:
👉 (动态类型信息, 数据指针)
为什么 i == nil 为 false?
题目:
var x *int = nil
var i interface{} = x
fmt.Println(i == nil)
-
x是一个*int类型的 nil 值。 -
赋值给接口
i时,Go 会把i变成:- 动态类型:
*int - 数据指针:
nil
- 动态类型:
因此 i 的内部表示是:
(_type = *int, data = nil)
而真正的 nil 接口 表示为:
(_type = nil, data = nil)
所以 i != nil。
👉 结论:接口等于 nil 的条件是「类型信息为 nil 且数据指针为 nil」。
3. int 和 int32 有什么区别?
-
int
Go 里的int是一种 平台相关的有符号整数类型,它的大小和 CPU 架构有关:- 在 32 位架构上:
int= 32 位(4 字节) - 在 64 位架构上:
int= 64 位(8 字节)
- 在 32 位架构上:
-
int32
Go 明确规定int32就是 固定 32 位的有符号整数(4 字节),和平台无关。
Go 是强类型语言,int 和 int32 是不同的类型,不能直接赋值,需要显式转换:
var a int32 = 100
var b int = a // ❌ 编译错误
var b int = int(a) // ✅ 必须显式转换
4. 哪些情况会发生fatal error?
- fatal error:不可以recover,或者说recover根本就不是针对fatal error这种场景来用的。
- panic:可以recover
-
map并发读写
-
栈溢出 stack overflow
-
内存溢出 out of memory
-
死锁。 all goroutines are asleep - deadlock!
-
将nil函数作为goroutine启动。fatal error: go of nil func value
var f func() go f()
5. defer 函数参数的加载时机
我们都知道defer函数执行是后进先出。 知道defer函数可以改变函数的返回值,也就是在return语句求值之后,再更改命名返回值的值。
但是你知道吗,defer 函数的参数,就是正常走到那行就求值的。
这个点是《go语言圣经》说了的。