Hello World In C Without Linking In Libraries

If there’s one constant with software developers, it is that sometimes they get bored. At these times, they tend to think dangerous thoughts, usually starting with ‘What if…’. Next you know, they have gone down a dark and winding rabbit hole and found themselves staring at something so amazing that the only natural conclusion that comes to mind is that while educational, it serves no immediate purpose.

The idea of applying this to snipping out the <stdio.h> header in C and the printf() function that it provides definitely is a good example here. Starting from the typical Hello World example in C, [Old Man Yells at Code] over at YouTube first takes us from the standard dynamically linked binary at a bloated 16 kB, to the statically linked version at an eyepopping 767 kB.

To remove any such dynamic linkages, and to keep file sizes somewhat sane, he then proceeds to first use the write()function from the <unistd.h> header, which does indeed cut out the <stdio.h> include, before doing the reasonable thing and removing all includes by rewriting the code in x86 assembly.

While this gets the final binary size down to 9 kB and needs no libraries to link with, it still performs a syscall, after setting appropriate register values, to hand control back to the kernel for doing the actual printing. If you try doing something similar with syscall(), you have to link in libc, so it might very well be that this is the real way to do Hello World without includes or linking in libraries. Plus the asm keyword is part of C, although one could argue that at this point you could just as well write everything in x86 ASM.

Of course, one cannot argue that this experience isn’t incredibly educational, and decidedly answers the original ‘What if…’ question.

27 thoughts on “Hello World In C Without Linking In Libraries

  1. Here’s a link to my own software library that includes serial output functions, and a “hello world” program that prints out a heartbeat and echoes typed chars in hex and is 9K bytes long… everything included, including the interrupt table, serial port setup, and ISR for sending characters.

    https://hackaday.io/project/177652-arduino-libraries-and-test-programs

    I use the code extensively in my projects and for clients, specifically because it’s tiny compared to the STDIO library, it has a tiny memory footprint, and all code is available for verification. (FAA certification does not allow code that will never be called, such as the %p formatting function).

    No huge stdio buffer, if you queue up more than 16 characters the system will block until complete. That’s usually not a problem, you usually only use serial output for debugging and it’s more important to use almost no memory than it is to avoid blocking.

    At some point in the process the code has to be able to send a single char out the serial port, and that can be either an OS function call, a mapped I/O register that the OS allows access to, or (in the case of arduino) the I/O register itself.

    Beyond that, I don’t see how “Hello World” takes up even 16KB.

        1. For Windows development, I normally use Winforms or WPF. A year or so back, not wanting to be a luddite, I tentatively essayed my first WinUI3 project. However, I didn’t want to distribute it via the Microsoft Store, so generated a local build.
          A simple Hello World level program resulted in dozens of files (IIRC) with a combined size of (again, IIRC…) north of 130MB.

          I have gone back to WPF/WinForms.

    1. I have a similar thing here,
      https://github.com/T3sl4co1l/Reverb/blob/master/console.c
      This project is among my “do it yourself and understand why before using libraries” sorts of projects, so it makes some peculiar choices at times, I’m sure; this version is also rather old (not that the core console / command / bufserial trio has changed much over the years). Anyway, these basically implement a primitive interactive shell over serial (the relevant parts of bufserial.c and .h can be changed for most any platform), which I use for bespoke “printf debugging” and basic dev testing (e.g. direct port read/write, SPI and I2C drivers for testing external peripherals, etc.).

  2. It is always a trade off between quick and dirty vs highly optimized code. We have an embarrassment of riches in terms of memory and processing power today. Is it better to use a bunch of RAM or programming time? If the code runs fast and terminates, like a utility, its probably not worth it to optimize (like etcher). If it runs continuously like a spreadsheet, a browser, or a database it makes more sense to tune it more. It would be nice if it were easier to rip out just the functions you need from a library rather than imcluding the entire bundle.

    1. Last time I checked(in gcc-avr, not x86) that exactly what happens during linking stage of executable building, removing all unused functions from libraries, and your own code. It’s called garbage collection if I’m not mistaken.

      1. Removing unused functions is form of link-time optimization, or LTO.
        The advantage of an MCU is there is no OS if you code bare metal. Prior to the main function it just needs to set up the C runtime environment by initializing variables. And there is nothing to return to after main.

  3. And they say there’s no such thing as progress…:-(

    Back in the day, which would 1974 or 5, I was programming in COBOL on a Honeywell 6060 running the GCOS-III operating system.

    Pretty primitive in many ways by today’s standards, however the linker was much better;

    I t would pull from the library only those routines which were actually called by the program being linked.

    Six or so years later, I was programming on Multics which had the first (and still best) dynamic linking system

    Both necessary on systems which only had 768KB (the GCOS) and 4-8 MB (the various Multics systems I used) to play with. (And, in addition to the batch programs, many timesharing users to support at the same time)

    But hey, RAM is so cheap now, let’s forget about memory efficiency if the linker is faster. (Ignoring the fact that once the program goes live it will run potentially thousands or millions of times with any need for relinking).

    Yes, I am getting old, but some things really were done better in the Olden Days.

    1. That’s what static linking is STILL supposed to do. But when nobody bats an eye when you list a system requirement of 16 GB RAM for a simple application, people just get sloppy. If you are using a compiler/linker that just packs the whole library into your “statically linked” executable, you are using a broken compiler. Which you may not even notice on your desktop machine, but I think many of us would investigate when we’re trying to program a microcontroller with 8 kB of program memory and this sort of nonsense pops up.

    2. This reminds me of something Microsoft did in Windows XP: they wanted to make sure that people’s software would still run when they upgraded, so they tested all of the popular applications. They found that there were so many applications with bugs like using allocated memory after it was freed, the only solution was to make free() do NOTHING. Memory only got freed when you quit the application.

Leave a Reply to BrightBlueJimCancel reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.