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 | 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 動態鏈接庫依賴的問題,這點是非常值得稱贊的。
原文:?http://www.cnxct.com/why-golang-elf-binary-file-is-large-than-c/
轉載于:https://www.cnblogs.com/MaAce/p/8716702.html
總結
以上是生活随笔為你收集整理的golang语言编译的二进制可执行文件为什么比 C 语言大(转载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: usermod命令
- 下一篇: 25、【华为HCIE-Storage】-