[Golang 修仙之路] Go基础:冷门杂题

43 阅读3分钟

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 字节)
  • int32
    Go 明确规定 int32 就是 固定 32 位的有符号整数(4 字节),和平台无关。


Go 是强类型语言,intint32不同的类型,不能直接赋值,需要显式转换:

var a int32 = 100
var b int = a      // ❌ 编译错误
var b int = int(a) // ✅ 必须显式转换

image.png


4. 哪些情况会发生fatal error?

  • fatal error:不可以recover,或者说recover根本就不是针对fatal error这种场景来用的。
  • panic:可以recover
  1. map并发读写

  2. 栈溢出 stack overflow

  3. 内存溢出 out of memory

  4. 死锁。 all goroutines are asleep - deadlock!

  5. 将nil函数作为goroutine启动。fatal error: go of nil func value

    var f func()
    go f()
    

5. defer 函数参数的加载时机

我们都知道defer函数执行是后进先出。 知道defer函数可以改变函数的返回值,也就是在return语句求值之后,再更改命名返回值的值。

但是你知道吗,defer 函数的参数,就是正常走到那行就求值的。

这个点是《go语言圣经》说了的。