golang语言编译的二进制可执行文件为什么比 C 语言大(转载)

最近一位朋友问我“为什么同样的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

[email protected]:/home/cfc4n/go_vs_c# gcc -o c.out main.c

[email protected]:/home/cfc4n/go_vs_c# go build -o go_fmt.out main_fmt.go

[email protected]:/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

[email protected]:/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

[email protected]:/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

 [email protected]:/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

[email protected]:/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

  [email protected]:/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

[email protected]:/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)

[email protected]:/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

[email protected]:/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

[email protected]:/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

[email protected]:/home/cfc4n/go_vs_c# gcc -static -o c_static_gs.out -g -s main.c

[email protected]:/home/cfc4n/go_vs_c# go build -o go_fmt_sw.out -ldflags="-s -w" main_fmt.go

[email protected]:/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

[email protected]:/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

[email protected]:/home/cfc4n/go_vs_c# go build -o go_print.out main_print.go

[email protected]:/home/cfc4n/go_vs_c# go build -o go_print_sw.out -ldflags="-s -w" main_print.go

[email protected]:/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 动态链接库依赖的问题,这点是非常值得称赞的。

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

原文地址:https://www.cnblogs.com/MaAce/p/8716702.html

时间: 2024-09-29 16:21:38

golang语言编译的二进制可执行文件为什么比 C 语言大(转载)的相关文章

14. Go 语言编译与工具

Go 语言编译与工具 Go 语言的工具链非常丰富,从获取源码.编译.文档.测试.性能分析,到源码格式化.源码提示.重构工具等应有尽有. 在 Go 语言中可以使用测试框架编写单元测试,使用统一的命令行即可测试及输出测试报告的工作.基准测试提供可自定义的计时器和一套基准测试算法,能方便快速地分析一段代码可能存在的 CPU 耗用和内存分配问题.性能分析工具可以将程序的 CPU 耗用.内存分配.竞态问题以图形化方式展现出来. go build命令(go语言编译命令)完全攻略 Go语言的编译速度非常快.G

被extern &quot;C&quot; 修饰后按照C语言编译

录屏软件 FSCapture extern "C" c语言编译 会发现报错,C语言不支持函数重载 #include <iostream> using namespace std; extern "C" void func() { } extern "C" void func(int v) { } int main() { getchar(); return 0; } 还可以extern "C" {}全部包含修饰:只

C语言编译全过程

原文地址:http://www.yuanma.org/data/2009/0506/article_3647.htm 编译的概念:编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序.    编译的完整过程:C源程序-->预编译处理(.c)-->编译.优化程序(.s..asm)-->汇编程序(.obj..o..a..ko)-->链接程序(.exe..elf

编译型与解释型、动态语言与静态语言、强类型语言与弱类型语言的区别

一.编译型和解释型 我们先看看编译型,其实它和汇编语言是一样的:也是有一个负责翻译的程序来对我们的源代码进行转换,生成相对应的可执行代码.这个过程说得专业一点,就称为编译(Compile),而负责编译的程序自然就称为编译器(Compiler).如果我们写的程序代码都包含在一个源文件中,那么通常编译之后就会直接生成一个可执行文件,我们就可以直接运行了.但对于一个比较复杂的项目,为了方便管理,我们通常把代码分散在各个源文件中,作为不同的模块来组织.这时编译各个文件时就会生成目标文件(Object  

用VC编译lua源码,生成lua语言的解释器和编译器

用VC编译lua源码,生成lua语言的解释器和编译器 1.去网址下载源码 http://www.lua.org/download.html 2.装一个VC++,我用的是VC6.0 3.接下来我们开始编译源码,我们需要编译: 一个静态库 一个动态库 一个lua语言解释器 一个lua编译器 建立一个工静态库工程 打开VC-->文件-->(点击)新建--(弹出框中选择)工程-->(win32 static library) 创一个空的工程 工程名为luaLib 把lua中所有的源码添加,去掉其

C语言编译全过程【转】

http://www.linuxdiyf.com/viewarticle.php?id=175655 编译的概念:编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序. 编译的完整过程:C源程序-->预编译处理(.c)-->编译.优化程序(.s..asm)-->汇编程序(.obj..o..a..ko)-->链接程序(.exe..elf..axf等) 1.

python\java\c\解释性语言\编译性语言 程序执行过程

c时纯粹的编译性语言,执行过程如下图: 可以看出c是通过预处理器.编译器.汇编器和链接器生成可执行的二进制目标文件,然后直接运行(编译性语言的本质).  Java和python是解释性语言,但并不是纯粹的解释性语言,准确来说是源文件被解释器加载或者显式地进行字节码编译之后,生成字节码文件,运行时字节码文件直接在解释器上边解释边运行(解释性语言的本质). java执行过程: a.java(源文件)----->a.class(字节码文件)----->解释器上直接运行 python执行过程 a.py

lua编译为二进制方式

当不想使用户看到lua源码,文本文件可以通过luac,把lua文本文件“编译”成二进制的文件. [email protected]:~/lua$ luac -o redis-white redis-white.lua [email protected]:~/lua$ cat redis-white uaS? xV([email protected]@[email protected]&? localhost? redis_host redis_port_ENV 编译后的文件还需要lua解析器才可

C语言编译出现 incompatible implicit declaration of built-in function ‘strlen’等

C语言 编译时出现错误 warning: incompatible implicit declaration of built-in function 'malloc'warning: incompatible implicit declaration of built-in function 'bzero'warning: incompatible implicit declaration of built-in function 'strncpy'warning: incompatible