annidy / notes

0 stars 0 forks source link

struct layout #289

Open annidy opened 1 month ago

annidy commented 1 month ago

C语言的结构体上按“字”(WORD)对齐的,而Go语言中是按类型的整数倍对齐。举例来说

func main() {
    type A1 struct {
        a bool
        b int16
        c int
    }
    var a1 A1
    fmt.Println(unsafe.Sizeof(a1), unsafe.Offsetof(a1.a), unsafe.Offsetof(a1.b), unsafe.Offsetof(a1.c))
}

64位下,输出“16 0 2 8” 32位下,输出“8 0 2 4”

简单改一下上面的代码,看看反汇编的结果

func main() {
    type A1 struct {
        a bool
        b int16
        c int
    }
    var a1 A1
    a1.b = 12
}

反汇编代码go tool compile -N -l -S main.go

TEXT    main.main(SB), NOSPLIT|ABIInternal, $24-0
PUSHQ   BP
MOVQ    SP, BP
SUBQ    $16, SP
FUNCDATA        $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
FUNCDATA        $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
MOVB    $0, main.a1(SP)
MOVW    $0, main.a1+2(SP)
MOVQ    $0, main.a1+8(SP)
MOVW    $12, main.a1+2(SP)
ADDQ    $16, SP
POPQ    BP
RET

代码SUBQ $16, SP,可以看到,a1是在栈上生成的 后面会对每个结构体字段用0初始化

MOVB    $0, main.a1(SP)
MOVW    $0, main.a1+2(SP)
MOVQ    $0, main.a1+8(SP)

Plan9汇编提供小于字对MOV指令。可以确认的是,结构体中的“空洞”并没初始化。

var a1 A1改为var a1 = new(A1),发现还是在栈上生成的(逃逸分析)

TEXT    main.main(SB), NOSPLIT|ABIInternal, $32-0
PUSHQ   BP
MOVQ    SP, BP
SUBQ    $24, SP
FUNCDATA        $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
FUNCDATA        $1, gclocals·Plqv2ff52JtlYaDd2Rwxbg==(SB)
MOVB    $0, main..autotmp_1(SP)
MOVW    $0, main..autotmp_1+2(SP)
MOVQ    $0, main..autotmp_1+8(SP)
LEAQ    main..autotmp_1(SP), AX
MOVQ    AX, main.a1+16(SP)
TESTB   AL, (AX)
MOVW    $12, 2(AX)
ADDQ    $24, SP
POPQ    BP
RET

这里指针多占用了一个字。

我们打破逃逸分析,试一下

func foo() *A1 {
    a := new(A1)
    a.b = 12
    return a
}

反汇编结果

TEXT    main.foo(SB), ABIInternal, $40-0
CMPQ    SP, 16(R14)
PCDATA  $0, $-2
JLS     65  ; 栈空间不够,跳转到下面扩栈
PCDATA  $0, $-1
PUSHQ   BP
MOVQ    SP, BP
SUBQ    $32, SP
FUNCDATA        $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
FUNCDATA        $1, gclocals·EaPwxsZ75yY1hHMVZLmk6g==(SB)
MOVQ    $0, main.~r0+16(SP)
LEAQ    type:main.A1(SB), AX
PCDATA  $1, $0
NOP
CALL    runtime.newobject(SB)
MOVQ    AX, main.a+24(SP)
MOVW    $12, 2(AX)
MOVQ    main.a+24(SP), AX
MOVQ    AX, main.~r0+16(SP)
ADDQ    $32, SP
POPQ    BP
NOP
RET
NOP
PCDATA  $1, $-1
PCDATA  $0, $-2
CALL    runtime.morestack_noctxt(SB)
PCDATA  $0, $-1
JMP     0

现在是调CALL runtime.newobject(SB)分配内存了