0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[golang] グローバル変数は、Data segment というメモリ領域に配置される

Posted at

目的

グローバル変数は、スタックでもヒープでもなく、Data segment というメモリ静的メモリ領域に配置されることを理解する。
下記の書籍に、ヒープにエスケープされると書いてあるが、これは間違いで、ヒープに配置されるわけではない。

変数がヒープにエスケープされる他の場合として、次のようなものがあります。

  • グローバル変数(複数のゴルーチンがアクセスできるので)

環境

$ go version
go version go1.23.2 linux/amd64

検証

(1)

myValueというグローバル変数は、0x54a128( 54a128 )というメモリ領域に配置されていることがわかる。
このメモリ領域は、スタックでもヒープでもなく、Data segmentという静的メモリ領域である。
lea 0xb9887(%rip),%rdx # 54a128 <main.myValue> という記述は、rip アドレスから、rdx アドレスにデータをロードしており、rip はグローバル変数のみに使われるアドレスの種類である。
ベンチマークテストでは、0x64e1e0というアドレスになっていて、go runとかの結果とは異なるが、Data segment に配置されることには変わりない。

package main

import "fmt"

var myValue = 64 * 1024

func main() {
	fmt.Printf("%p\n", &myValue)
}

package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}

$ go build -gcflags "-m=2" .
# gopractice
./main.go:7:6: cannot inline main: function too complex: cost 81 exceeds budget 80
./main.go:8:12: inlining call to fmt.Printf
./main.go:8:12: ... argument does not escape
$ go test -bench=. -benchmem
...
0x64e1e0
0x64e1e0
...

  301125              6292 ns/op               0 B/op          0 allocs/op
PASS
ok      gopractice      1.942s
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000490880 <main.main>:
  490880:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  490884:	76 53                	jbe    4908d9 <main.main+0x59>
  490886:	55                   	push   %rbp
  490887:	48 89 e5             	mov    %rsp,%rbp
  49088a:	48 83 ec 48          	sub    $0x48,%rsp
  49088e:	48 8d 15 ab 66 00 00 	lea    0x66ab(%rip),%rdx        # 496f40 <type:*+0x5f40>
  490895:	48 89 54 24 38       	mov    %rdx,0x38(%rsp)
  49089a:	48 8d 15 87 98 0b 00 	lea    0xb9887(%rip),%rdx        # 54a128 <main.myValue>
  4908a1:	48 89 54 24 40       	mov    %rdx,0x40(%rsp)
  4908a6:	48 8b 1d bb 3b 0c 00 	mov    0xc3bbb(%rip),%rbx        # 554468 <os.Stdout>
  4908ad:	48 8d 05 04 50 04 00 	lea    0x45004(%rip),%rax        # 4d58b8 <go:itab.*os.File,io.Writer>
  4908b4:	48 8d 0d e9 f6 01 00 	lea    0x1f6e9(%rip),%rcx        # 4affa4 <go:string.*+0x4c>
  4908bb:	bf 03 00 00 00       	mov    $0x3,%edi
  4908c0:	48 8d 74 24 38       	lea    0x38(%rsp),%rsi
  4908c5:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  4908cb:	4d 89 c1             	mov    %r8,%r9
  4908ce:	e8 6d 98 ff ff       	call   48a140 <fmt.Fprintf>
  4908d3:	48 83 c4 48          	add    $0x48,%rsp
  4908d7:	5d                   	pop    %rbp
  4908d8:	c3                   	ret
  4908d9:	e8 02 93 fd ff       	call   469be0 <runtime.morestack_noctxt.abi0>
  4908de:	66 90                	xchg   %ax,%ax
  4908e0:	eb 9e                	jmp    490880 <main.main>
$ go run main.go
0x54a128

(2)

0x54fa0054fa00)は、DataSegment 領域のアドレスである。これは、グローバル変数であるスライス構造体のアドレスを示している。
スライスは、容量を超えたappendをすると、容量が2倍となるスライスのコピーを作り(スライス要素のみコピーする)、ヒープに配置する。
今回は、容量を超えたappendをしたので、容量二倍のスライス要素のコピーがヒープ領域に作成された。しかし、スライス構造体自体のアドレスは変わっていない。

package main

import "fmt"

var myValue = []int{1, 2, 3, 4, 5}

func main() {
	fmt.Printf("Slice struct address: %p\n", &myValue)          // スライス構造のアドレス
	fmt.Printf("Data address before append: %p\n", &myValue[0]) // データ領域のアドレス
	myValue = append(myValue, 6)
	fmt.Printf("Slice struct address after append: %p\n", &myValue) // スライス構造のアドレス
	fmt.Printf("Data address after append: %p\n", &myValue[0])      // 新しいデータ領域のアドレス
}

package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}

$ go build -gcflags "-m=2" .
# gopractice
./main.go:7:6: cannot inline main: function too complex: cost 333 exceeds budget 80
./main.go:8:12: inlining call to fmt.Printf
./main.go:9:12: inlining call to fmt.Printf
./main.go:11:12: inlining call to fmt.Printf
./main.go:12:12: inlining call to fmt.Printf
./main.go:8:12: ... argument does not escape
./main.go:9:12: ... argument does not escape
./main.go:11:12: ... argument does not escape
./main.go:12:12: ... argument does not escape
$ go test -bench=. -benchmem
...
Slice struct address: 0x661e80
Data address before append: 0xc000154000
Slice struct address after append: 0x661e80
Data address after append: 0xc000154000
Slice struct address: 0x661e80
Data address before append: 0xc000154000
Slice struct address after append: 0x661e80
Data address after append: 0xc000154000
...
   10000            149283 ns/op              34 B/op          0 allocs/op
PASS
ok      gopractice      1.503s
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000490880 <main.main>:
  490880:	4c 8d 64 24 f8       	lea    -0x8(%rsp),%r12
  490885:	4d 3b 66 10          	cmp    0x10(%r14),%r12
  490889:	0f 86 b7 01 00 00    	jbe    490a46 <main.main+0x1c6>
  49088f:	55                   	push   %rbp
  490890:	48 89 e5             	mov    %rsp,%rbp
  490893:	48 83 c4 80          	add    $0xffffffffffffff80,%rsp
  490897:	48 8d 15 62 7c 00 00 	lea    0x7c62(%rip),%rdx        # 498500 <type:*+0x7500>
  49089e:	48 89 54 24 70       	mov    %rdx,0x70(%rsp)
  4908a3:	4c 8d 15 56 f1 0b 00 	lea    0xbf156(%rip),%r10        # 54fa00 <main.myValue>
  4908aa:	4c 89 54 24 78       	mov    %r10,0x78(%rsp)
  4908af:	48 8b 1d 12 3c 0c 00 	mov    0xc3c12(%rip),%rbx        # 5544c8 <os.Stdout>
  4908b6:	48 8d 05 3b 51 04 00 	lea    0x4513b(%rip),%rax        # 4d59f8 <go:itab.*os.File,io.Writer>
  4908bd:	48 8d 0d 01 30 02 00 	lea    0x23001(%rip),%rcx        # 4b38c5 <go:string.*+0x392d>
  4908c4:	bf 19 00 00 00       	mov    $0x19,%edi
  4908c9:	48 8d 74 24 70       	lea    0x70(%rsp),%rsi
  4908ce:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  4908d4:	4d 89 c1             	mov    %r8,%r9
  4908d7:	e8 64 98 ff ff       	call   48a140 <fmt.Fprintf>
  4908dc:	48 83 3d 24 f1 0b 00 	cmpq   $0x0,0xbf124(%rip)        # 54fa08 <main.myValue+0x8>
  4908e3:	00 
  4908e4:	0f 86 51 01 00 00    	jbe    490a3b <main.main+0x1bb>
  4908ea:	48 8b 15 0f f1 0b 00 	mov    0xbf10f(%rip),%rdx        # 54fa00 <main.myValue>
  4908f1:	4c 8d 15 48 66 00 00 	lea    0x6648(%rip),%r10        # 496f40 <type:*+0x5f40>
  4908f8:	4c 89 54 24 60       	mov    %r10,0x60(%rsp)
  4908fd:	48 89 54 24 68       	mov    %rdx,0x68(%rsp)
  490902:	48 8b 1d bf 3b 0c 00 	mov    0xc3bbf(%rip),%rbx        # 5544c8 <os.Stdout>
  490909:	48 8d 05 e8 50 04 00 	lea    0x450e8(%rip),%rax        # 4d59f8 <go:itab.*os.File,io.Writer>
  490910:	48 8d 0d 66 46 02 00 	lea    0x24666(%rip),%rcx        # 4b4f7d <go:string.*+0x4fe5>
  490917:	bf 1f 00 00 00       	mov    $0x1f,%edi
  49091c:	48 8d 74 24 60       	lea    0x60(%rsp),%rsi
  490921:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  490927:	4d 89 c1             	mov    %r8,%r9
  49092a:	e8 11 98 ff ff       	call   48a140 <fmt.Fprintf>
  49092f:	48 8b 0d da f0 0b 00 	mov    0xbf0da(%rip),%rcx        # 54fa10 <main.myValue+0x10>
  490936:	48 8b 1d cb f0 0b 00 	mov    0xbf0cb(%rip),%rbx        # 54fa08 <main.myValue+0x8>
  49093d:	48 ff c3             	inc    %rbx
  490940:	48 8b 05 b9 f0 0b 00 	mov    0xbf0b9(%rip),%rax        # 54fa00 <main.myValue>
  490947:	48 39 d9             	cmp    %rbx,%rcx
  49094a:	73 3b                	jae    490987 <main.main+0x107>
  49094c:	bf 01 00 00 00       	mov    $0x1,%edi
  490951:	48 8d 35 28 90 00 00 	lea    0x9028(%rip),%rsi        # 499980 <type:*+0x8980>
  490958:	e8 03 5b fd ff       	call   466460 <runtime.growslice>
  49095d:	48 89 0d ac f0 0b 00 	mov    %rcx,0xbf0ac(%rip)        # 54fa10 <main.myValue+0x10>
  490964:	83 3d c5 3c 0e 00 00 	cmpl   $0x0,0xe3cc5(%rip)        # 574630 <runtime.writeBarrier>
  49096b:	74 13                	je     490980 <main.main+0x100>
  49096d:	e8 ee af fd ff       	call   46b960 <runtime.gcWriteBarrier2>
  490972:	49 89 03             	mov    %rax,(%r11)
  490975:	48 8b 15 84 f0 0b 00 	mov    0xbf084(%rip),%rdx        # 54fa00 <main.myValue>
  49097c:	49 89 53 08          	mov    %rdx,0x8(%r11)
  490980:	48 89 05 79 f0 0b 00 	mov    %rax,0xbf079(%rip)        # 54fa00 <main.myValue>
  490987:	48 89 1d 7a f0 0b 00 	mov    %rbx,0xbf07a(%rip)        # 54fa08 <main.myValue+0x8>
  49098e:	48 c7 44 d8 f8 06 00 	movq   $0x6,-0x8(%rax,%rbx,8)
  490995:	00 00 
  490997:	48 8d 15 62 7b 00 00 	lea    0x7b62(%rip),%rdx        # 498500 <type:*+0x7500>
  49099e:	48 89 54 24 50       	mov    %rdx,0x50(%rsp)
  4909a3:	48 8d 15 56 f0 0b 00 	lea    0xbf056(%rip),%rdx        # 54fa00 <main.myValue>
  4909aa:	48 89 54 24 58       	mov    %rdx,0x58(%rsp)
  4909af:	48 8b 1d 12 3b 0c 00 	mov    0xc3b12(%rip),%rbx        # 5544c8 <os.Stdout>
  4909b6:	48 8d 05 3b 50 04 00 	lea    0x4503b(%rip),%rax        # 4d59f8 <go:itab.*os.File,io.Writer>
  4909bd:	48 8d 0d 68 5d 02 00 	lea    0x25d68(%rip),%rcx        # 4b672c <go:string.*+0x6794>
  4909c4:	bf 26 00 00 00       	mov    $0x26,%edi
  4909c9:	48 8d 74 24 50       	lea    0x50(%rsp),%rsi
  4909ce:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  4909d4:	4d 89 c1             	mov    %r8,%r9
  4909d7:	e8 64 97 ff ff       	call   48a140 <fmt.Fprintf>
  4909dc:	48 83 3d 24 f0 0b 00 	cmpq   $0x0,0xbf024(%rip)        # 54fa08 <main.myValue+0x8>
  4909e3:	00 
  4909e4:	76 4b                	jbe    490a31 <main.main+0x1b1>
  4909e6:	48 8b 15 13 f0 0b 00 	mov    0xbf013(%rip),%rdx        # 54fa00 <main.myValue>
  4909ed:	4c 8d 15 4c 65 00 00 	lea    0x654c(%rip),%r10        # 496f40 <type:*+0x5f40>
  4909f4:	4c 89 54 24 40       	mov    %r10,0x40(%rsp)
  4909f9:	48 89 54 24 48       	mov    %rdx,0x48(%rsp)
  4909fe:	48 8b 1d c3 3a 0c 00 	mov    0xc3ac3(%rip),%rbx        # 5544c8 <os.Stdout>
  490a05:	48 8d 05 ec 4f 04 00 	lea    0x44fec(%rip),%rax        # 4d59f8 <go:itab.*os.File,io.Writer>
  490a0c:	48 8d 0d 32 41 02 00 	lea    0x24132(%rip),%rcx        # 4b4b45 <go:string.*+0x4bad>
  490a13:	bf 1e 00 00 00       	mov    $0x1e,%edi
  490a18:	48 8d 74 24 40       	lea    0x40(%rsp),%rsi
  490a1d:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  490a23:	4d 89 c1             	mov    %r8,%r9
  490a26:	e8 15 97 ff ff       	call   48a140 <fmt.Fprintf>
  490a2b:	48 83 ec 80          	sub    $0xffffffffffffff80,%rsp
  490a2f:	5d                   	pop    %rbp
  490a30:	c3                   	ret
  490a31:	31 c0                	xor    %eax,%eax
  490a33:	48 89 c1             	mov    %rax,%rcx
  490a36:	e8 c5 b2 fd ff       	call   46bd00 <runtime.panicIndex>
  490a3b:	31 c0                	xor    %eax,%eax
  490a3d:	48 89 c1             	mov    %rax,%rcx
  490a40:	e8 bb b2 fd ff       	call   46bd00 <runtime.panicIndex>
  490a45:	90                   	nop
  490a46:	e8 95 91 fd ff       	call   469be0 <runtime.morestack_noctxt.abi0>
  490a4b:	e9 30 fe ff ff       	jmp    490880 <main.main>
$ go run main.go
Slice struct address: 0x54fa00
Data address before append: 0x54a420
Slice struct address after append: 0x54fa00
Data address after append: 0xc00008c000

おまけ

下記資料からもわかるように、スライスは、スライス構造体であり、そのスライス構造体のフィールドの一つが、スライス要素のポインタを示すのである。

// []byte のイメージ。Go のコンパイラが実際にこういう構造体を管理しているわけではない
type sliceHeader struct {
    Length        int
    Capacity      int
    ZerothElement *byte
}

参考

RIP addressing is always relative to RIP (64bit Instruction Pointer) register. So it can be use for global variables only. The 0 offset is equal to address of the following instruction after the RIP-addressed instruction. For example:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?