首页 > 为什么go语言的hello world程序编译后有2M那么大?

为什么go语言的hello world程序编译后有2M那么大?

对比一下go语言和c语言编译hello world后的大小,go语言版本有2M大小,而C语言版本仅有8k

go 代码:

package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}

c代码:

#include <stdio.h>
int main()
{
    printf("hello world!\n");
    return 0;
}

大小对比:

# du * -sh
8.0K    a.out
2.2M    hello
4.0K    hello.c
4.0K    hello.go

照着Linux C man文档inotify的例程给PHPDroid写了个C程序,
在Android App卸载删除文件时,捕获IN_DELETE_SELF事件,退出PHP进程.
静态链接,交叉编译,生成的二进制文件也才500多KB.
Go生成的程序比较大可能是因为其内置了GC垃圾回收还有goroutine协程这些机制的实现.
像Go这种无依赖的运行模式还是挺好的,我Ubuntu上静态链接交叉编译的Linux ARM版PHP,用xz打包后不到5.5MB,拿到Android和Raspbian上都能运行,这是一种摆脱依赖地狱的良好体验.

#define _GNU_SOURCE
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <unistd.h>

/*

http://man7.org/linux/man-pages/man7/inotify.7.html
http://man7.org/linux/man-pages/man3/getline.3.html

arm-none-linux-gnueabi-gcc watcher.c -o watcher \
-Os -Xlinker -static -static-libgcc --sysroot=/png/dev/android/glibc
arm-none-linux-gnueabi-strip watcher
xz -z -k -9 watcher

gcc watcher.c -o watcher -Os -static
strip watcher

touch /home/eechen/note/watcher/auth.php
./watcher /home/eechen/note/watcher/auth.php >/dev/null 2>&1 &

*/

char* get_pid(char* filename) {
    FILE *stream;
    char *line = NULL;
    size_t len = 0;
    ssize_t read;

    stream = fopen(filename, "r");
    if (stream == NULL)
        exit(EXIT_FAILURE);

    if ((read = getline(&line, &len, stream)) != -1) {
        printf("Retrieved line of length %zu : ", read);
        printf("%s", line);
    }

    //free(line);
    fclose(stream);
    //exit(EXIT_SUCCESS);
    return line;
}

void stop_pid(char* pid) {
    char* command = NULL;
    command =  malloc(strlen("kill -9 ") + strlen(pid) + 1);
    if (command) {
        strcpy(command, "kill -9 ");
        strcat(command, pid);
        //execl("/bin/sh", "sh", "-c", command, NULL);
        execl("/system/bin/sh", "sh", "-c", command, NULL);
    }
    exit(EXIT_SUCCESS);
}

/* Read all available inotify events from the file descriptor 'fd'.
  wd is the table of watch descriptors for the directories in argv.
  argc is the length of wd and argv.
  argv is the list of watched directories.
  Entry 0 of wd and argv is unused. */

static void
handle_events(int fd, int *wd, int argc, char* argv[], char* pid)
{
    /* Some systems cannot read integer variables if they are not
    properly aligned. On other systems, incorrect alignment may
    decrease performance. Hence, the buffer used for reading from
    the inotify file descriptor should have the same alignment as
    struct inotify_event. */

    char buf[4096]
    __attribute__ ((aligned(__alignof__(struct inotify_event))));
    const struct inotify_event *event;
    int i;
    ssize_t len;
    char *ptr;
    int ret;

    /* Loop while events can be read from inotify file descriptor. */

    for (;;) {

        /* Read some events. */

        len = read(fd, buf, sizeof buf);
        if (len == -1 && errno != EAGAIN) {
            perror("read");
            exit(EXIT_FAILURE);
        }

        /* If the nonblocking read() found no events to read, then
        it returns -1 with errno set to EAGAIN. In that case,
        we exit the loop. */

        if (len <= 0)
            break;

        /* Loop over all events in the buffer */

        for (ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) {

            event = (const struct inotify_event *) ptr;

            /* Print event type */
            
            if (event->mask & IN_OPEN) {
                printf("IN_OPEN: ");
            }
            if (event->mask & IN_DELETE_SELF) {
                stop_pid(pid);
            }

            /* Print the name of the watched directory */

            for (i = 1; i < argc; ++i) {
                if (wd[i] == event->wd) {
                    printf("%s/", argv[i]);
                    break;
                }
            }            
            
            /* Print the name of the file */

            if (event->len)
                printf("%s", event->name);
                        
            /* Print type of filesystem object */

            if (event->mask & IN_ISDIR)
                printf(" [directory]\n");
            else
                printf(" [file]\n");
        }
    }
}

int
main(int argc, char* argv[])
{
    char buf;
    int fd, i, poll_num;
    int *wd;
    nfds_t nfds;
    struct pollfd fds[1];

    if (argc < 2) {
        printf("Usage: %s PATH [PATH ...]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Create the file descriptor for accessing the inotify API */

    fd = inotify_init1(IN_NONBLOCK);
    if (fd == -1) {
        perror("inotify_init1");
        exit(EXIT_FAILURE);
    }

    /* Allocate memory for watch descriptors */

    wd = calloc(argc, sizeof(int));
    if (wd == NULL) {
        perror("calloc");
        exit(EXIT_FAILURE);
    }
    
    /* Mark directories for events */

    for (i = 1; i < argc; i++) {
        wd[i] = inotify_add_watch(fd, argv[i], IN_DELETE_SELF);
        if (wd[i] == -1) {
            fprintf(stderr, "Cannot watch '%s'\n", argv[i]);
            perror("inotify_add_watch");
            exit(EXIT_FAILURE);
        }
    }

    char *pid;
    pid = get_pid("pid");
    //getline生成的堆内存变量return后在函数外free释放
    //free(pid);
    
    /* Prepare for polling */

    nfds = 1;

    /* Inotify input */

    fds[0].fd = fd;
    fds[0].events = POLLIN;

    /* Wait for events and/or terminal input */

    printf("Listening for events.\n");
    
    while (1) {
        poll_num = poll(fds, nfds, -1);
        if (poll_num == -1) {
            if (errno == EINTR)
                continue;
            perror("poll");
            exit(EXIT_FAILURE);
        }

        if (poll_num > 0) {
            if (fds[0].revents & POLLIN) {

                /* Inotify events are available */

                handle_events(fd, wd, argc, argv, pid);
            }
        }
    }

    printf("Listening for events stopped.\n");

    /* Close inotify file descriptor */

    close(fd);

    free(wd);
    exit(EXIT_SUCCESS);
}

我的系统是:CentOS release 6.5 (Final)
strip go 版 hello执行文件后大小是1.6M。

如果把a.out依赖的库改名,那么这个程序就不能执行了。

可以看到cp ls mv等命令都不能执行了,go版本的hello还能执行,我要恢复系统先!23333

正在想办法恢复系统.... 如果提前准备一套go版的bash命令要包括cd,mv,就好了!

将hello.c 静态编译后的大小对比:

gcc -static hello.c

[root@ctos helloworld]# ll -h
total 2.3M
-rwxr-xr-x. 1 root root 671K May  2 17:20 a.out
-rwxr-xr-x. 1 root root 1.6M May  2 10:48 hello
-rw-r--r--. 1 root root   65 May  2 08:14 hello.c
-rw-r--r--. 1 root root   78 May  2 08:10 hello.go
-rwxr-xr-x. 1 root root 111K May  2 17:22 mv

[root@ctos helloworld]# file a.out 
a.out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.18, stripped
[root@ctos helloworld]# file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

从这个意义上讲,go 的编译基本上相当于 gcc -static。静态链接的C程序也要大很多,因为所有库函数都包含在内了。


谢谢邀请,有几个问题
1) c版的main后面要返回0,否则编译时会有warning。
2) go版的hello world默认编译出来的a.out是有符号表的,你如果想要去掉符号表,需要
strip -s main
在RHEL6.4上,go1.4 strip -s之后,go版本的hello world只有1.3MB

3) go当时希望解决目前语言的一个问题就是部署时的库依赖,能够迅速的、无缝的部署,试想,如果有一个服务器程序要发布,这个服务器程序依赖于差不多100多个依赖的动态库,每次你加入新的feature之后,依赖的动态库要升级,服务器程序本身也要升级,这在部署的时候是非常麻烦的,所以go推崇的程序发布方式是将依赖的库和程序本身一起发布,也就是说你用go编译出来的程序,不会再依赖其他的库了,你直接拷贝过去就能用!是的,直接拷贝就能用!! java程序要发布,你需要打包JRE环境,python程序要发布,你需要打包python解释器,而go不用。所以去掉符号表的go版的hello world差不多1.3MB,这个在目前硬盘白菜价的市场上,基本上都能接受。(go编译选项https://golang.org/cmd/link/)
4) C版的hello world我们来看下,都依赖什么。

其中光libc的依赖库就差不多2MB了。

所以从依赖库的角度来看,go版的hello world表现尚可,还在接受范围之内。

希望上述回答能解决你的问题。

【热门文章】
【热门文章】