反直觉的 go panic log 行号

2025-09-07 • 1.2分钟 • 0.5千字

线上出现了一个奇怪的问题, panic 日志打印的函数栈信息不太符合直觉. 反复检查了代码和线上 commit 号都没有问题.

先看线上的伪代码:

func foo(arg1, arg2, arg3 string) string {
        return arg1 + arg2 + arg3
}

func main() {
        var m map[int]*string
    
        _ = foo(
                *m[1],
                *m[1],
                fmt.Sprintf(*m[1]),
        )
    
        fmt.Println("done")
}

panic 信息在第 11 行, 直觉上应该在第 9 行. 因为函数参数的调用顺序应该跟代码的顺序对应起来.

验证函数的参数执行循序

func foo(arg1 string) string {
    fmt.Println(arg1)
    return arg1
}

func bar(arg1, arg2, arg3 string) string {
    return arg1 + arg2 + arg3
}

func main() {
    _ = bar(
        foo("1"),
        foo("2"),
        foo("3"),
    )

    fmt.Println("done")
}

这段代码用来测试函数参数的执行顺序, 输出如下:

1
2
3
done

说明函数的参数确实是按照参数顺序执行的, 那为什么 panic 日志不符合预期?

查看Order_of_evaluation, 但没有明确说明函数参数里混合 map 和函数的时候的执行顺序.

反编译, 看看内脏

这里为了方便理解, 提供全部代码.

package main 

import "fmt"

func foo(arg1, arg2, arg3 string) string {
        return arg1 + arg2 + arg3
}

func main() {
        var m map[int]*string
    
        _ = foo(
                *m[1],
                *m[1],
                fmt.Sprintf(*m[1]),
        )
    
        fmt.Println("done")
}

执行 go tool objdump -s 'main' main > objdump.txt, 得到反编译结果:

  main.go:15        0x10009df3c     f00001c0        ADRP 241664(PC), R0         
  main.go:15        0x10009df40     91020000        ADD $128, R0, R0            
  main.go:15        0x10009df44     aa1f03e1        MOVD ZR, R1             
  main.go:15        0x10009df48     b24003e2        ORR $1, ZR, R2              
  main.go:15        0x10009df4c     97fd9f11        CALL runtime.mapaccess1_fast64(SB)  
  main.go:15        0x10009df50     f9400003        MOVD (R0), R3               
  main.go:15        0x10009df54     f9400060        MOVD (R3), R0               
  main.go:15        0x10009df58     f9400461        MOVD 8(R3), R1              
  main.go:15        0x10009df5c     aa1f03e2        MOVD ZR, R2             
  main.go:15        0x10009df60     aa1f03e3        MOVD ZR, R3             
  main.go:15        0x10009df64     aa0303e4        MOVD R3, R4             
  main.go:15        0x10009df68     97ffe6d6        CALL fmt.Sprintf(SB)            
  main.go:15        0x10009df6c     f9003fe0        MOVD R0, 120(RSP)           
  main.go:15        0x10009df70     f90037e1        MOVD R1, 104(RSP)           
  main.go:13        0x10009df74     f00001c0        ADRP 241664(PC), R0         
  main.go:13        0x10009df78     91020000        ADD $128, R0, R0            
  main.go:13        0x10009df7c     aa1f03e1        MOVD ZR, R1             
  main.go:13        0x10009df80     b24003e2        ORR $1, ZR, R2              
  main.go:13        0x10009df84     97fd9f03        CALL runtime.mapaccess1_fast64(SB)  
  main.go:13        0x10009df88     f9400003        MOVD (R0), R3               
  main.go:13        0x10009df8c     f9400064        MOVD (R3), R4               
  main.go:13        0x10009df90     f90043e4        MOVD R4, 128(RSP)           
  main.go:13        0x10009df94     f9400463        MOVD 8(R3), R3              
  main.go:13        0x10009df98     f9003be3        MOVD R3, 112(RSP)           
  main.go:14        0x10009df9c     f00001c0        ADRP 241664(PC), R0         
  main.go:14        0x10009dfa0     91020000        ADD $128, R0, R0            
  main.go:14        0x10009dfa4     aa1f03e1        MOVD ZR, R1             
  main.go:14        0x10009dfa8     b24003e2        ORR $1, ZR, R2              
  main.go:14        0x10009dfac     97fd9ef9        CALL runtime.mapaccess1_fast64(SB)  
  main.go:14        0x10009dfb0     f9400003        MOVD (R0), R3               
  main.go:14        0x10009dfb4     f9400064        MOVD (R3), R4               
  main.go:14        0x10009dfb8     f9400463        MOVD 8(R3), R3              
  main.go:12        0x10009dfbc     d503201f        NOOP                    
  main.go:6         0x10009dfc0     910123e0        ADD $72, RSP, R0            
  main.go:6         0x10009dfc4     f94043e1        MOVD 128(RSP), R1           
  main.go:6         0x10009dfc8     f9403be2        MOVD 112(RSP), R2           
  main.go:6         0x10009dfcc     f9403fe5        MOVD 120(RSP), R5           
  main.go:6         0x10009dfd0     f94037e6        MOVD 104(RSP), R6           
  main.go:14        0x10009dfd4     aa0303e7        MOVD R3, R7             
  main.go:6         0x10009dfd8     aa0403e3        MOVD R4, R3             
  main.go:6         0x10009dfdc     aa0703e4        MOVD R7, R4             
  main.go:6         0x10009dfe0     97fecdb4        CALL runtime.concatstring3(SB)      

15 13 14 都进行了 runtime.mapaccess1_fast64(SB), 就是 对 m 的 map 访问, 而 15 行被提前了, 其次是 13 14 行. NOOP 后是 foo 的函数实现, 因为编译优化产生了内敛.

关闭内联优化go build -gcflags="all=-l -N" .:

  main.go:15        0x10009c938     90000120        ADRP 147456(PC), R0         
  main.go:15        0x10009c93c     912f0000        ADD $3008, R0, R0           
  main.go:15        0x10009c940     aa1f03e1        MOVD ZR, R1             
  main.go:15        0x10009c944     b24003e2        ORR $1, ZR, R2              
  main.go:15        0x10009c948     97fdab2a        CALL runtime.mapaccess1_fast64(SB)  
  main.go:15        0x10009c94c     f9400003        MOVD (R0), R3               
  main.go:15        0x10009c950     f9400060        MOVD (R3), R0               
  main.go:15        0x10009c954     f9400461        MOVD 8(R3), R1              
  main.go:15        0x10009c958     aa1f03e2        MOVD ZR, R2             
  main.go:15        0x10009c95c     aa1f03e3        MOVD ZR, R3             
  main.go:15        0x10009c960     aa0303e4        MOVD R3, R4             
  main.go:15        0x10009c964     97fff15b        CALL fmt.Sprintf(SB)            
  main.go:15        0x10009c968     f90027e0        MOVD R0, 72(RSP)            
  main.go:15        0x10009c96c     f9001fe1        MOVD R1, 56(RSP)            
  main.go:13        0x10009c970     90000120        ADRP 147456(PC), R0         
  main.go:13        0x10009c974     912f0000        ADD $3008, R0, R0           
  main.go:13        0x10009c978     aa1f03e1        MOVD ZR, R1             
  main.go:13        0x10009c97c     b24003e2        ORR $1, ZR, R2              
  main.go:13        0x10009c980     97fdab1c        CALL runtime.mapaccess1_fast64(SB)  
  main.go:13        0x10009c984     f9400003        MOVD (R0), R3               
  main.go:13        0x10009c988     f9400064        MOVD (R3), R4               
  main.go:13        0x10009c98c     f90033e4        MOVD R4, 96(RSP)            
  main.go:13        0x10009c990     f9400463        MOVD 8(R3), R3              
  main.go:13        0x10009c994     f90023e3        MOVD R3, 64(RSP)            
  main.go:14        0x10009c998     90000120        ADRP 147456(PC), R0         
  main.go:14        0x10009c99c     912f0000        ADD $3008, R0, R0           
  main.go:14        0x10009c9a0     aa1f03e1        MOVD ZR, R1             
  main.go:14        0x10009c9a4     b24003e2        ORR $1, ZR, R2              
  main.go:14        0x10009c9a8     97fdab12        CALL runtime.mapaccess1_fast64(SB)  
  main.go:14        0x10009c9ac     f9400003        MOVD (R0), R3               
  main.go:14        0x10009c9b0     f9400062        MOVD (R3), R2               
  main.go:14        0x10009c9b4     f9400463        MOVD 8(R3), R3              
  main.go:12        0x10009c9b8     f94033e0        MOVD 96(RSP), R0            
  main.go:12        0x10009c9bc     f94023e1        MOVD 64(RSP), R1            
  main.go:12        0x10009c9c0     f94027e4        MOVD 72(RSP), R4            
  main.go:12        0x10009c9c4     f9401fe5        MOVD 56(RSP), R5            
  main.go:12        0x10009c9c8     97ffffb6        CALL main.foo(SB)           
  main.go:18        0x10009c9cc     d00000e3        ADRP 122880(PC), R3         
  main.go:18        0x10009c9d0     91158063        ADD $1376, R3, R3           

我们可以看到了对 foo 函数的调用, 但是调用顺序依然没有变化.

这就很显然了, 当函数里的参数是函数时, 关闭优化和开启优化都会先执行参数是函数的参数.

结论

order

一般情况下, 函数会按照从左到右的顺序执行, 如果有函数那么会优先函数, 如果是 map/slice 那里面的顺序并没有明确说明, 切不受编译优化开关影响, 至少现在没有明确的开关可以控制这个顺序.

下面这些 case 都是未定义行为:

a := 1
f := func() int { a++; return a }

x := []int{a, f()}            // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified
m := map[int]int{a: 1, a: 2}  // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified
n := map[int]int{a: f()}      // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified

也就是没有明确的执行循序.