golang語言編譯的二進制可執行文件爲什麼比 C 語言大

https://www.cnxct.com/why-golang-elf-binary-file-is-large-than-c/

golang語言編譯的二進制可執行文件爲什麼比 C 語言大

 2017/09/21  CFC4N

最近一位朋友問我“爲什麼同樣的hello world 入門程序”爲什麼golang編譯出來的二進制文件,比 C 大,而且大很多。我做了個測試,來分析這個問題。C 語言的hello world程序:

1

2

3

4

5

#include <stdio.h>

int main() {

    printf("hello world!\n");

    return 0;

}

golang 語言的hello world程序:

1

2

3

4

5

6

7

package main

 

import "fmt"

 

func main() {

    fmt.Printf("hello, world!\n")

}

編譯,查看生成文件大小

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

root@cnxct:/home/cfc4n/go_vs_c# gcc -o c.out main.c

root@cnxct:/home/cfc4n/go_vs_c# go build -o go_fmt.out main_fmt.go

root@cnxct:/home/cfc4n/go_vs_c# ll

total 1552

drwxr-xr-x 2 root  root     4096 Sep 20 16:56 ./

drwxr-xr-x 8 cfc4n cfc4n    4096 Sep 20 16:54 ../

-rwxr-xr-x 1 root  root     8600 Sep 20 16:56 c.out*

-rwxr-xr-x 1 root  root  1560062 Sep 20 16:56 go_fmt.out*

-rw-r--r-- 1 root  root       78 Sep 20 16:54 main.c

-rw-r--r-- 1 root  root       78 Sep 20 16:55 main_fmt.go

root@cnxct:/home/cfc4n/go_vs_c# du -sh *

12K c.out

1.5M    go_fmt.out

4.0K    main.c

4.0K    main_fmt.go

正如這位朋友所說c.out是12K,而 go_fmt.out是1.5M,差距奇大無比….爲什麼呢?

這兩個二進制可執行文件文件裏,都包含了什麼?

衆所周知,linux 上的二進制可執行文件是 ELF Executable and Linkable Format 可執行和可鏈接格式

ELF文件格式組成

 

如上圖,ELF 文件分爲如下:

  • ELF文件的組成:ELF header
  • 程序頭:描述段信息
  • Section頭:鏈接與重定位需要的數據
  • 程序頭與Section頭需要的數據.text .data

在 Linux 上, 查看elf格式構成可以使用readelf

ELF Header:頭的信息

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

root@cnxct:/home/cfc4n/go_vs_c# readelf -h c.out

ELF Header:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  Class:                             ELF64

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              EXEC (Executable file)

  Machine:                           Advanced Micro Devices X86-64

  Version:                           0x1

  Entry point address:               0x400430

  Start of program headers:          64 (bytes into file)

  Start of section headers:          6616 (bytes into file)

  Flags:                             0x0

  Size of this header:               64 (bytes)

  Size of program headers:           56 (bytes)

  Number of program headers:         9

  Size of section headers:           64 (bytes)

  Number of section headers:         31

  Section header string table index: 28

 

 root@cnxct:/home/cfc4n/go_vs_c# readelf -h go_fmt.out

ELF Header:

  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

  Class:                             ELF64

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              EXEC (Executable file)

  Machine:                           Advanced Micro Devices X86-64

  Version:                           0x1

  Entry point address:               0x44e360

  Start of program headers:          64 (bytes into file)

  Start of section headers:          456 (bytes into file)

  Flags:                             0x0

  Size of this header:               64 (bytes)

  Size of program headers:           56 (bytes)

  Number of program headers:         7

  Size of section headers:           64 (bytes)

  Number of section headers:         23

  Section header string table index: 3

ELF 頭的長度都是一樣的,不會帶來總體體積的變化。區別是個別字節的值不一樣,比如Entry point address 程序入口點的值不一樣等。

接下來是 程序頭:,也就是 section部分(在linker連接器的角度是section部分或者裝載器角度的segment)

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

root@cnxct:/home/cfc4n/go_vs_c# readelf -d c.out

 

Dynamic section at offset 0xe28 contains 24 entries:

  Tag        Type                         Name/Value

 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

 0x000000000000000c (INIT)               0x4003c8

 0x000000000000000d (FINI)               0x4005b4

 0x0000000000000019 (INIT_ARRAY)         0x600e10

 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)

 0x000000000000001a (FINI_ARRAY)         0x600e18

 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)

 0x000000006ffffef5 (GNU_HASH)           0x400298

 0x0000000000000005 (STRTAB)             0x400318

 0x0000000000000006 (SYMTAB)             0x4002b8

 0x000000000000000a (STRSZ)              61 (bytes)

 0x000000000000000b (SYMENT)             24 (bytes)

 0x0000000000000015 (DEBUG)              0x0

 0x0000000000000003 (PLTGOT)             0x601000

 0x0000000000000002 (PLTRELSZ)           48 (bytes)

 0x0000000000000014 (PLTREL)             RELA

 0x0000000000000017 (JMPREL)             0x400398

 0x0000000000000007 (RELA)               0x400380

 0x0000000000000008 (RELASZ)             24 (bytes)

 0x0000000000000009 (RELAENT)            24 (bytes)

 0x000000006ffffffe (VERNEED)            0x400360

 0x000000006fffffff (VERNEEDNUM)         1

 0x000000006ffffff0 (VERSYM)             0x400356

 0x0000000000000000 (NULL)               0x0

可以看到c.out裏引用了一個動態鏈接庫libc.so.6,再看下go_fmt.out的情況

1

2

3

  root@cnxct:/home/cfc4n/go_vs_c# readelf -d go_fmt.out

 

There is no dynamic section in this file.

c.out的執行,依賴了libc.so.6, libc.so.6肯定需要ld.so的,看下依賴情況,

1

2

3

4

5

6

7

root@cnxct:/home/cfc4n/go_vs_c# ldd c.out

    linux-vdso.so.1 =>  (0x00007fff3a195000)

    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4ac4d06000)

    /lib64/ld-linux-x86-64.so.2 (0x0000558ece3fe000)

 

root@cnxct:/home/cfc4n/go_vs_c# ldd go_fmt.out

    not a dynamic executable

依賴了libc.so這個動態鏈接庫

也就是說,C的程序默認使用了libc.so動態鏈接庫,go 的程序,默認進行了靜態編譯,不依賴任何動態鏈接庫。所以體積變大了。 那麼,只是這一個原因嗎?

我在 golang 的官方文檔裏找到如下的解釋:

Why is my trivial program such a large binary?
The linker in the gc tool chain creates statically-linked binaries by default. All Go binaries therefore include the Go run-time, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.

A simple C “hello, world” program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf is around 1.5 MB, but that includes more powerful run-time support and type information.

將c的程序也使用靜態編譯試試。。。

1

2

3

4

5

6

7

gcc -static -o c_static.out main.c

root@cnxct:/home/cfc4n/go_vs_c# du -sh *

12K c.out

888K    c_static.out

1.5M    go_fmt.out

4.0K    main.c

4.0K    main_fmt.go

可以看到,使用靜態編譯生成的二進制文件c_static.out爲888K,仍然比 GO 寫的小了一半,這到底是爲什麼呢?到底是哪裏多了?

在ELF 可執行文件裏,就需要以程序編譯鏈接的角度來分析了,對於一個 ELF 文件的分析,上面部分分析過 ELF header部分,以及 dynamic section的情況了。再以看一下剩餘的section信息。

鏈接器視圖與加載器視圖

ELF中的section主要提供給Linker使用, 而segment提供給Loader用,Linker需要關心.text, .rel.text, .data, .rodata等等,關鍵是Linker需要做relocation。而Loader只需要知道這個段的Read、Write、Execute的屬性。

再去看go_fmt.out裏都包含了什麼,爲了方便校對,寫了一個程序來對比

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

package main

 

import (

    "debug/elf"

    "fmt"

    "os"

)

 

func main() {

    if len(os.Args) != 3 {

        fmt.Println("參數不對")

        os.Exit(0)

    }

 

    strFile1 := os.Args[1]

    strFile2 := os.Args[2]

    f1, e := elf.Open(strFile1)

    if e != nil {

        panic(e)

    }

 

    f2, e := elf.Open(strFile2)

    if e != nil {

        panic(e)

    }

    mapSection1 := make(map[string]string, 0)

    mapSection2 := make(map[string]string, 0)

 

    //[Nr]    Name    Type    Address    Offset    Size    EntSize    Flags    Link    Info    Align

    var size1 uint64

    var size2 uint64

    for _, s := range f1.Sections {

        mapSection1[s.Name] = fmt.Sprintf("%s\t%s\t%s\t%010x\t%010x\t%d\t%x\t%s\t%x\t%x\t%x\t", s.Name, strFile1, s.Type.String(), s.Addr, s.Offset, s.Size, s.Entsize, s.Flags.String(), s.Link, s.Info, s.Addralign)

        size1 += s.Size

    }

 

    for _, s := range f2.Sections {

        mapSection2[s.Name] = fmt.Sprintf("%s\t%s\t%s\t%010x\t%010x\t%d\t%x\t%s\t%x\t%x\t%x\t", s.Name, strFile2, s.Type.String(), s.Addr, s.Offset, s.Size, s.Entsize, s.Flags.String(), s.Link, s.Info, s.Addralign)

        size2 += s.Size

    }

 

    fmt.Println(fmt.Sprintf("%s:%d\t%s:%d", strFile1, size1, strFile2, size2))

 

    fmt.Println("Name\tFile\tType\tAddress\tOffset\tSize\tEntSize\tFlags\tLink\tInfo\tAlign")

    for k, v := range mapSection1 {

        fmt.Println(v)

        if v1, found := mapSection2[k]; found {

            fmt.Println(v1)

            delete(mapSection2, k)

        }

    }

 

    for _, v := range mapSection2 {

        fmt.Println(v)

    }

}

對比一下兩個文件的section段信息

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

root@cnxct:/home/cfc4n/go_vs_c# ./diffelf c_static.out go_fmt.out

c_static.out:910462 go_fmt.out:1674012

Name    File    Type    Address Offset  Size    EntSize Flags   Link    Info    Align

.init_array c_static.out    SHT_INIT_ARRAY  00006c8ed8  00000c8ed8  16  0   SHF_WRITE+SHF_ALLOC 00  8

.fini_array c_static.out    SHT_FINI_ARRAY  00006c8ee8  00000c8ee8  16  0   SHF_WRITE+SHF_ALLOC 00  8

.data   c_static.out    SHT_PROGBITS    00006c9080  00000c9080  6864    0   SHF_WRITE+SHF_ALLOC 0   020

.data   go_fmt.out  SHT_PROGBITS    00004fa4e0  00000fa4e0  7440    0   SHF_WRITE+SHF_ALLOC 0   020

.strtab c_static.out    SHT_STRTAB  0000000000  00000d6b40  26703   0   0x0 0   0   1

.strtab go_fmt.out  SHT_STRTAB  0000000000  00001704e0  51486   0   0x0 0   0   1

__libc_subfreeres   c_static.out    SHT_PROGBITS    00004bd6a8  00000bd6a8  80  0   SHF_ALLOC   00  8

__libc_thread_subfreeres    c_static.out    SHT_PROGBITS    00004bd708  00000bd708  8   0   SHF_ALLOC   0   0   8

.got    c_static.out    SHT_PROGBITS    00006c8fe8  00000c8fe8  16  8   SHF_WRITE+SHF_ALLOC 0   08

.comment    c_static.out    SHT_PROGBITS    0000000000  00000cab50  52  1   SHF_MERGE+SHF_STRINGS   00  1

.fini   c_static.out    SHT_PROGBITS    00004a0470  00000a0470  9   0   SHF_ALLOC+SHF_EXECINSTR 0   04

.eh_frame   c_static.out    SHT_PROGBITS    00004bd710  00000bd710  44948   0   SHF_ALLOC   0   08

.bss    c_static.out    SHT_NOBITS  00006cab60  00000cab50  6264    0   SHF_WRITE+SHF_ALLOC 0   020

.bss    go_fmt.out  SHT_NOBITS  00004fc200  00000fc200  108808  0   SHF_WRITE+SHF_ALLOC 0   020

.gcc_except_table   c_static.out    SHT_PROGBITS    00004c86a4  00000c86a4  179 0   SHF_ALLOC   00  1

.tdata  c_static.out    SHT_PROGBITS    00006c8eb8  00000c8eb8  32  0   SHF_WRITE+SHF_ALLOC+SHF_TLS 00  8

.note.gnu.build-id  c_static.out    SHT_NOTE    00004001b0  00000001b0  36  0   SHF_ALLOC   00  4

__libc_freeres_fn   c_static.out    SHT_PROGBITS    000049de60  000009de60  9513    0   SHF_ALLOC+SHF_EXECINSTR 0   0   10

.plt    c_static.out    SHT_PROGBITS    00004002f0  00000002f0  160 0   SHF_ALLOC+SHF_EXECINSTR 0   010

.note.stapsdt   c_static.out    SHT_NOTE    0000000000  00000cab84  3864    0   0x0 0   0   4

.symtab c_static.out    SHT_SYMTAB  0000000000  00000cbaa0  45216   18  0x0 20  2c7 8

.symtab go_fmt.out  SHT_SYMTAB  0000000000  0000164000  50400   18  0x0 16  5f  8

.note.ABI-tag   c_static.out    SHT_NOTE    0000400190  0000000190  32  0   SHF_ALLOC   0   04

.rela.plt   c_static.out    SHT_RELA    00004001d8  00000001d8  240 18  SHF_ALLOC+SHF_INFO_LINK018  8

.rodata c_static.out    SHT_PROGBITS    00004a0480  00000a0480  119332  0   SHF_ALLOC   0   0   20

.rodata go_fmt.out  SHT_PROGBITS    000047e000  000007e000  212344  0   SHF_ALLOC   0   0   20

.data.rel.ro    c_static.out    SHT_PROGBITS    00006c8f00  00000c8f00  228 0   SHF_WRITE+SHF_ALLOC 00  20

.init   c_static.out    SHT_PROGBITS    00004002c8  00000002c8  26  0   SHF_ALLOC+SHF_EXECINSTR 0   04

.text   c_static.out    SHT_PROGBITS    0000400390  0000000390  645828  0   SHF_ALLOC+SHF_EXECINSTR 0   010

.text   go_fmt.out  SHT_PROGBITS    0000401000  0000001000  508779  0   SHF_ALLOC+SHF_EXECINSTR 0   010

__libc_atexit   c_static.out    SHT_PROGBITS    00004bd6f8  00000bd6f8  8   0   SHF_ALLOC   0   08

.stapsdt.base   c_static.out    SHT_PROGBITS    00004bd700  00000bd700  1   0   SHF_ALLOC   0   01

.jcr    c_static.out    SHT_PROGBITS    00006c8ef8  00000c8ef8  8   0   SHF_WRITE+SHF_ALLOC 0   08

    c_static.out    SHT_NULL    0000000000  0000000000  0   0   0x0 0   0   0

    go_fmt.out  SHT_NULL    0000000000  0000000000  0   0   0x0 0   0   0

__libc_thread_freeres_fn    c_static.out    SHT_PROGBITS    00004a0390  00000a0390  222 0   SHF_ALLOC+SHF_EXECINSTR 0   0   10

__libc_freeres_ptrs c_static.out    SHT_NOBITS  00006cc3d8  00000cab50  48  0   SHF_WRITE+SHF_ALLOC 0   0   8

.shstrtab   c_static.out    SHT_STRTAB  0000000000  00000dd38f  361 0   0x0 0   0   1

.shstrtab   go_fmt.out  SHT_STRTAB  0000000000  00000b1d80  257 0   0x0 0   0   1

.tbss   c_static.out    SHT_NOBITS  00006c8ed8  00000c8ed8  48  0   SHF_WRITE+SHF_ALLOC+SHF_TLS 00  8

.got.plt    c_static.out    SHT_PROGBITS    00006c9000  00000c9000  104 8   SHF_WRITE+SHF_ALLOC 00  8

.itablink   go_fmt.out  SHT_PROGBITS    00004b29d8  00000b29d8  56  0   SHF_ALLOC   0   08

.gopclntab  go_fmt.out  SHT_PROGBITS    00004b2a20  00000b2a20  282414  0   SHF_ALLOC   0   020

.debug_abbrev   go_fmt.out  SHT_PROGBITS    000051c000  00000fd000  255 0   0x0 0   0   1

.debug_frame    go_fmt.out  SHT_PROGBITS    000052b2d5  000010c2d5  69564   0   0x0 0   0   1

.debug_aranges  go_fmt.out  SHT_PROGBITS    000054634c  000012734c  48  0   0x0 0   0   1

.debug_info go_fmt.out  SHT_PROGBITS    00005463a6  00001273a6  248638  0   0x0 0   0   1

.note.go.buildid    go_fmt.out  SHT_NOTE    0000400fc8  0000000fc8  56  0   SHF_ALLOC   00  4

.debug_pubtypes go_fmt.out  SHT_PROGBITS    000053e9e5  000011f9e5  31079   0   0x0 0   0   1

.debug_gdb_scripts  go_fmt.out  SHT_PROGBITS    000054637c  000012737c  42  0   0x0 0   01

.debug_line go_fmt.out  SHT_PROGBITS    000051c0ff  00000fd0ff  61910   0   0x0 0   0   1

.typelink   go_fmt.out  SHT_PROGBITS    00004b1ea0  00000b1ea0  2872    0   SHF_ALLOC   0   020

.noptrdata  go_fmt.out  SHT_PROGBITS    00004f8000  00000f8000  9416    0   SHF_WRITE+SHF_ALLOC 00  20

.gosymtab   go_fmt.out  SHT_PROGBITS    00004b2a10  00000b2a10  0   0   SHF_ALLOC   0   01

.noptrbss   go_fmt.out  SHT_NOBITS  0000516b20  0000116b20  18080   0   SHF_WRITE+SHF_ALLOC 00  20

.debug_pubnames go_fmt.out  SHT_PROGBITS    000053c291  000011d291  10068   0   0x0 0   0   1

發現go_fmt.out多了好多.debug_*開頭的 section,這是用於 debug 的段信息。再次編譯,去除這些信息,同時也把 C 靜態編譯的二進制也去除符號表和重定位信息。

1

2

3

4

5

6

7

8

9

root@cnxct:/home/cfc4n/go_vs_c# gcc -static -o c_static_gs.out -g -s main.c

root@cnxct:/home/cfc4n/go_vs_c# go build -o go_fmt_sw.out -ldflags="-s -w" main_fmt.go

 

root@cnxct:/home/cfc4n/go_vs_c# du -sh *

12K c.out

820K    c_static_gs.out

888K    c_static.out

1.5M    go_fmt.out

1012K   go_fmt_sw.out

如上結果,go_fmt_sw.out爲1012K,c_static_gs.out爲820K,還大了近200KB。到底是哪裏大的呢?

剛剛的兩個elf 文件的section對比中,還有一個比較特殊的go_fmt.out中 有一個名字叫.gopclntab的段,類型是SHT_PROGBITS程序段,大小爲 282414字節,也就是275K,在c_static.out裏並沒有這個段的,也沒有.gosymtab這個段。二者不一樣,section段名字有規範標準嗎?
其實,對於linker鏈接器來說,會關心段(section)的名字,但對loader加載器來說,並不關心名字,只關心這個段(segment)的權限,是否可執行,所在的偏移地址,用於函數的執行。

那.gopclntab段包含了什麼內容呢?我寫了一個程序分析了這個段的內容,程序代碼如下:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

package main

 

import (

    "debug/elf"

    "debug/gosym"

    "fmt"

    "os"

)

 

func main() {

    if len(os.Args) != 2 {

        fmt.Println("參數不對")

        os.Exit(0)

    }

 

    strFile1 := os.Args[1]

 

    f1, err := elf.Open(strFile1)

    if err != nil {

        panic(err)

    }

 

    symtab, err := f1.Section(".gosymtab").Data()

    if err != nil {

        f1.Close()

        panic(".gosymtab 異常")

    }

 

    gopclntab, err := f1.Section(".gopclntab").Data()

    if err != nil {

        f1.Close()

        panic(".gopclntab 異常")

    }

 

    pcln := gosym.NewLineTable(gopclntab, f1.Section(".text").Addr)

    var tab *gosym.Table

    tab, err = gosym.NewTable(symtab, pcln)

    if err != nil {

        f1.Close()

        panic(err)

    }

    for _, x := range tab.Funcs {

        fmt.Println(fmt.Sprintf("addr:0x%x\t\tname:%s,\t",x.Entry,x.Name))

    }

}

編譯後執行

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

root@cnxct:/home/cfc4n/go_vs_c# ./expelf go_fmt.out

addr:0x401000       name:sync/atomic.StoreUint32,  

addr:0x401010       name:sync/atomic.StoreUint64,  

addr:0x401020       name:sync/atomic.StoreUintptr, 

addr:0x401030       name:runtime.memhash0, 

addr:0x401040       name:runtime.memhash8, 

......

addr:0x427040       name:runtime.printnl,  

......

addr:0x44dc80       name:runtime.memmove,  

addr:0x44e360       name:_rt0_amd64_linux, 

addr:0x44e380       name:main, 

addr:0x44e390       name:runtime.exit

addr:0x44ea70       name:runtime.epollwait,

addr:0x44ea90       name:runtime.(*cpuProfile).(runtime.flushlog)-fm,  

addr:0x44eae0       name:type..hash.runtime.uncommontype,  

......

addr:0x452d60       name:math.init.1,  

addr:0x452e00       name:math.init,

addr:0x452e70       name:math.hasSSE4, 

addr:0x452e90       name:type..hash.[70]float64,   

addr:0x452f10       name:type..eq.[70]float64, 

addr:0x452f50       name:errors.New,   

addr:0x452ff0       name:errors.(*errorString).Error,  

......

addr:0x4534c0       name:unicode/utf8.RuneCountInString,   

addr:0x453600       name:strconv.(*decimal).String,

addr:0x453a00       name:strconv.digitZero,

addr:0x453a30       name:strconv.trim, 

addr:0x453aa0       name:strconv.(*decimal).Assign,

......

addr:0x4599c0       name:strconv.init, 

addr:0x459ad0       name:type..hash.strconv.decimal,   

addr:0x459ec0       name:type..eq.[61]strconv.leftCheat,   

addr:0x459f80       name:sync.(*Mutex).Lock,   

......

addr:0x45c6d0       name:syscall.Syscall6, 

addr:0x45c740       name:type..hash.[133]string,   

addr:0x45c7c0       name:type..eq.[133]string, 

addr:0x45c880       name:time.init,

addr:0x45df30       name:type..hash.os.PathError,  

addr:0x45dfc0       name:type..eq.os.PathError,

addr:0x45e0e0       name:reflect.makeMethodValue,  

addr:0x45eaf0       name:reflect.resolveReflectName,   

addr:0x45eb40       name:reflect.(*rtype).nameOff, 

......

addr:0x4710d0       name:reflect.(*funcTypeFixed64).Comparable,

addr:0x4710f0       name:type..hash.reflect.funcTypeFixed128,  

addr:0x471170       name:type..eq.reflect.funcTypeFixed128,

addr:0x471230       name:reflect.(*funcTypeFixed128).uncommon, 

...... 

addr:0x471c50       name:reflect.(*sliceType).Comparable,  

addr:0x471c70       name:type..hash.struct { reflect.b bool; reflect.x interface {} }, 

addr:0x471cf0       name:type..eq.struct { reflect.b bool; reflect.x interface {} },   

addr:0x471d70       name:type..hash.[27]string,

addr:0x471df0       name:type..eq.[27]string,  

addr:0x471ea0       name:fmt.(*fmt).writePadding,  

addr:0x472020       name:fmt.(*fmt).pad,   

......

addr:0x47b730       name:fmt.(*pp).badArgNum,  

addr:0x47b940       name:fmt.(*pp).missingArg, 

addr:0x47bb50       name:fmt.(*pp).doPrintf,   

addr:0x47cf70       name:fmt.glob..func1,  

addr:0x47cfd0       name:fmt.init, 

addr:0x47d170       name:type..hash.fmt.fmt,   

addr:0x47d1f0       name:type..eq.fmt.fmt

addr:0x47d2a0       name:main.main,

addr:0x47d310       name:main.init,

如上可以看到,有很多函數是以fmt.(*pp)、strconv.*、sync.*、reflect.*、unicode.*等開頭的,後面對應的函數名,也與 golang 的包裏對應的包中函數名一致。。。用 IDA來確認一遍
,果然在 .gopclntab 段裏有很多 reflect.*開頭的函數。
這就很奇怪了,golang 編譯時,默認把 runtime 包編譯進來就好了,應該不會把strconv\sync\reflect\unicode等包包含進來啊。程序中,只寫了一句fmt.Println(),莫非是fmt包import了其他幾個包導致的?回去搜了下代碼,果然…

嗯,應該是這裏問題,改用 go 的內置函數print試試。

1

2

3

4

5

package main

 

func main() {

   print("hello, world!\n")

}

編譯後,對比大小

01

02

03

04

05

06

07

08

09

10

11

12

13

root@cnxct:/home/cfc4n/go_vs_c# go build -o go_print.out main_print.go

root@cnxct:/home/cfc4n/go_vs_c# go build -o go_print_sw.out -ldflags="-s -w" main_print.go

root@cnxct:/home/cfc4n/go_vs_c# du -sh *

12K c.out

820K    c_static_gs.out

888K    c_static.out

1.5M    go_fmt.out

1012K   go_fmt_sw.out

940K    go_print.out

624K    go_print_sw.out

4.0K    main.c

4.0K    main_fmt.go

4.0K    main_print.go

看如上結果,go_print_sw.out 變成了 624K , c_static_gs.out爲820K,不光沒比C的靜態編譯的大,還比它小呢。。。 不過呢,這也不能說明什麼問題,只是因爲其包含的函數內容不一樣。

好了,至此已經知道爲什麼 golang 編譯的文件比 C 的大了,因爲 go 語言是靜態編譯的,而 C 的編譯(比如 gcc編譯器)都是動態鏈接庫形式編譯的。所以,導致了 go 編譯的文件稍微大的問題。其次,跟其他語言比較字符串輸出的話,用print內置函數就好了,就不要使用fmt包下的函數來比較了,因爲 fmt 包引入了好多其他的包。。。這也增加編譯後的二進制文件的體積。

其實呢,golang 的編譯(不涉及 cgo 編譯的前提下)默認使用了靜態編譯,不依賴任何動態鏈接庫,這樣可以任意部署到各種運行環境,不用擔心依賴庫的版本問題。只是體積大一點而已,存儲時佔用了一點磁盤,運行時,多佔用了一點內存。早期動態鏈接庫的產生,是因爲早期的系統的內存資源十分寶貴,由於內存緊張的問題在早期的系統中顯得更加突出,因此人們首先想到的是要解決內存使用效率不高這一問題,於是便提出了動態裝入的思想。也就產生了動態鏈接庫。在現在的計算機裏,操作系統的硬盤內存更大了,尤其是服務器,32G、64G 的內存都是最基本的。可以不用爲了節省幾百 KB 或者1M,幾 M 的內存而大大費周折了。而 golang 就採用這種做法,可以避免各種 so 動態鏈接庫依賴的問題,這點是非常值得稱讚的。

另外,.gopclntab 跟 .gosymtab段的討論,以及其他建議

程序員的自我修養

參考:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章