Hacker News new | past | comments | ask | show | jobs | submit login

Out of curiosity, I looked at a basic hello world in C, and compiled it naively using gcc on Ubuntu, by default the executable produced was 15960 bytes, but when adding the -Os option (from the man page: "-Os Optimize for size") to enable many optimisation flags including code size, I was able to reduce it to 15968.

  #include <stdio.h>
  int main() { printf("Hello, World!"); return 0;}
Of course, If I had read more than 3 words in the man page, the answer was easy to understand: "Os Optimize for size. -Os enables all -O2 optimizations except those that often increase code size", so you can't really get lower size than the default using only the optimization system, there's also "-finline-functions" included in -Os, but it won't help you in a Hello World.



This program is not same to the Rust version. A more faithful version, assuming glibc, would look like this:

    #include <stdio.h>
    #include <stdlib.h>
    #include <execinfo.h>
    #include <errno.h>
    
    void print_backtrace(void) {
        void *traces[50];
        char **symbols;
        int num_traces, i;
    
        num_traces = backtrace(traces, sizeof(traces) / sizeof(*traces));
        strings = backtrace_symbols(traces, num_traces);
        if (!strings) return;
        for (i = 0; i < num_traces; ++i) {
            fprintf("%d: %s\n", i + 1, strings[i]);
        }
        free(strings);
    }
    
    int main(void) {
        static const char FMT[] = "Hello, World!\n";
        static int EXPECTED = (int) (sizeof(FMT) - 1);
        int ret = printf(FMT);
        if (ret < 0) {
            fprintf(stderr, "printf failed: %s\n", strerror(errno));
            print_backtrace();
            return 1;
        }
        if (ret != EXPECTED) {
            fprintf(stderr, "printf failed: only %d characters were written\n", ret);
            print_backtrace();
            return 1;
        }
        return 0;
    }
While this is still substantially different (for example, Rust's I/O buffering is different from C), this should be enough to demonstrate that this comparison is very unfair.


Building your code (I fixed a few typos, strings -> symbols, fprintf(" -> fprintf(stderr, ") with

    gcc -s -Os -fuse-ld=lld a.c && ls -al a.out
leads to a 5496 bytes ELF though. Which is not much larger than just printf("Hello World!\n"), see sibling comment.

I think the point is C "cheated" by including a lot of goodness (format, backtrace etc) in the shared library so they does not have to be copied to each binary.


Thank you for the actual testing (and sorry for typos...). Yes, a C version is small because it dynamically links to libc.so in this case, and I meant to compare against a statically linked version. I primarily wrote this example as a response to the claim that an equivalent---it isn't---C code with musl is very small.


The problem is musl does not support execinfo. For a simple hello world, I managed to statically link with musl to get something with 7888 bytes.


    $ gcc -Os a.c && ls -al a.out && size
    -rwxr-xr-x 1 user user 15952 Jan 24 17:49 a.out
       text    data     bss     dec     hex filename
       1316     584       8    1908     774 a.out
    $ gcc -s -Os a.c && ls -al a.out && size
    -rwxr-xr-x 1 user user 14472 Jan 24 17:50 a.out
       text    data     bss     dec     hex filename
       1316     584       8    1908     774 a.out
    $ gcc -s -Os -fuse-ld=lld a.c && ls -al a.out && size
    -rwxr-xr-x 1 user user 4552 Jan 24 17:50 a.out
       text    data     bss     dec     hex filename
       1199     528       1    1728     6c0 a.out


    zig cc -Os -target x86_64-linux-musl hello.c -o hello
...which basically calls Clang under the hood, but comes with out-of-the-box cross-compilation support for Linux and MUSL creates a 5136 bytes executable.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: