Rickard Lindberg | Blog | Projects

How to write portable programs in C?

Written by Rickard Lindberg Profile picture of Rickard. in #c

If you want your programs to be maximally spread and survive for the longest period of time, C is probably the best language to write them in.

C is the lingua franca of programming. Many people know C and C compilers exist for most platforms. However, for a C program to do anything useful, it needs services from the platform. How to get that in a portable way?

The trick that I have learned is to separate your program into two parts. The largest part should be platform independent and contain most of the logic of the application. The smaller part should be a thin layer of platform specific code. One way to achieve that is like this:

app_linux.c:

char* platform_name(void) {
    return "linux";
}

#include "app.c"

int main(void) {
    return run();
}

app_windows.c:

char* platform_name(void) {
    return "windows";
}

#include "app.c"

int main(void) {
    return run();
}

app.c

#include <stdio.h>

int run(void) {
    printf("I'm running on %s\n", platform_name());
    return 0;
}

And it would be compiled like this (pretend that we use MSVC for the windows case):

$ gcc app_linux.c -o app && ./app
I'm running on linux

$ gcc app_windows.c -o app && ./app
I'm running on windows

That is, for each platform that you want your program to run on, you create app_<platform>.c and implement all the platform functions that your program needs. I learned about this approach from Handmade Hero.

When I first thought about how portable C programs could work, I imagined that I should be able to write the code once and then be able to run it everywhere without modification. That seemed impossibly hard. But with the separation of the platform layer it became obvious how to do it. You need to actively port your application to every platform that you want it to run on. But porting is not a big task. It's just to implement the platform functions that your application needs.

Designing this interface is certainly a skill. I believe the only way to get good at it is to write applications like this and port them to different platforms.

Another consequence of this separation is that the code in app.c should be truly platform independent. That also means that it can not depend on being compiled with a specific C compiler. In order to be maximally portable it should therefore be written in a version of C that is understood by most compilers. The website Dependable C tries to define such a subset of C. My understanding is that it is mostly just C89. (The platform layer, app_linux.c and app_windows.c, however, can make assumptions about the platform and compiler.)

One benefit of C is speed. You can write code that performs really well. However, in order to write code that performs well, you need to know how the hardware that executes it works and you need to know how the environment in which your program runs works. But when you write the platform independent code, you can't really make any assumptions about that. This I guess is a trade-off between portability and performance.

One way to get both might be to put performance critical parts of your program in the platform layer. Then each platform can implement it in the most appropriate way for that platform.

Another way might be to implement performance critical parts using multiple algorithms which can be chosen at compile time.

One consideration when writing platform independent code in C is that the sizes of integers can vary. For more information about that, see The C integer mystery and How should I decide which integer type to use?

This answer on Stack Overflow gives a possible reason why it is that way:

The C standard tries to strike a reasonable balance between portability, performance, and ease of implementation, meaning that there are compromises in all three areas.

If there are weaker requirements for the sizes of integers, it is probably easier to port the C language to different architectures. The C compiler does not need to generate special code to support a fixed width integer and can instead just use the native type of the architecture.

Recently I wrote some C code that needed to use an integer of at least 64 bits. I was wondering how to do that in a platform independent way. The only native C type that is defined to be at least 64 bits is long long and that is not defined until C99 (so not in C89). What to do?

The code that I wrote was not really intended to be platform independent. So I could have just stuck it in a platform layer and used a long long or whatever type was available on that platform that was at least 64 bits.

But let's say it was not. Let's say my program logic needed to deal with integers of at least 64 bits. What to do?

First of all that limits the number of platforms that my program can run on. At least in theory. Today, perhaps most interesting platforms support 64 bit integers? In the post Considering C99 for curl, Daniel describes how curl will assume that a 64 bit integer is available:

We have decided that starting with curl 8, we will require that the compiler supports a 64 bit data type. This is not something that existed in the original C89 version but was introduced in C99. However, there is no longer any modern compiler around that does not support this.

And from docs/INTERNALS.md:

We write curl and libcurl to compile with C89 compilers on 32-bit and up machines. Most of libcurl assumes more or less POSIX compliance but that is not a requirement. The compiler must support a 64-bit integer type as well as supply a stdint.h header file that defines C99-style fixed-width integer types like uint32_t.

I asked him how they use 64 bit integers in practice, and he replied

we can typedef our type based on what compiler/system that is used, but we use the standard int64_t/uint64_t if available

One way to interpret that in our portable design is that we put a typedef in the platform layer something like this:

typedef long long myint64;

Or if stdint.h is available:

#include <stdint.h>

typedef int64_t myint64;

I wrote this blog post mostly to clarify my thinking on how to write portable programs in C. But this is just a first step. Next, I hope to write more programs in C and port them to different platforms to further my knowledge in this area.

2026-04-28 week 18

What is Rickard working on and thinking about right now?

Every month I write a newsletter about just that. You will get updates about my current projects and thoughts about programming, and also get a chance to hit reply and interact with me. Subscribe to it below.

Powered by Buttondown (My Newsletter)

Profile picture of Rickard.

I'm Rickard Lindberg from Sweden. This is my home on the web. I like programming. I like both the craft of it and also to write software that solves problems. I also like running.

Me elsewhere: GitHub, Mastodon.