Why Test Your Code With Multiple Compilers

I don’t know whether this is surprising or not: Getting Vara to compile and run on BSD was way easier than getting the OS set up with a GUI. Eager to get Vara ported to BSD, I ignored the classic BSD variants and directly went for the ones I thought were friendlier. Guess what, couldn’t even finish downloading the MidnightBSD ISO due to the slow mirror. What about DragonflyBSD? It was a breeze to try live and install with QEMU, but adding a desktop (or any package) wouldn’t work even after setting up DHCP and pkg repos. I’m sure I made some mistake. Anyway, I moved on to GhostBSD.

I’d call GhostBSD the Ubuntu or Mint of the BSD world. It comes with a desktop, and everything works–except for the mouse pointer and the compilation of a basic Hello World program in C. Strange creatures we developers are, the latter was more urgent to me. The exact issue was, even with clang being installed by default, basic headers like stdio.h were missing. I tried installing gcc, which also failed to find the headers. find command confirmed that the files were indeed missing and this was not a matter of include path.

Turns out one has to execute pkg install -g GhostBSD*-dev for that (don’t forget the -g flag). It’s a set of development-related packages (similar to the -dev or -devel ones you’d encounter in a GNU/Linux distro), one of which gets the libc headers installed. Better to get all of it installed unless there is a specific reason.

Once it is installed, manual compilation of C files works fine. The next challenge is getting make to work. There are certain incompatibilities between GNU Make and BSD Make. For example, GNU Make uses ifdef while it is .ifdef in BSD. Fortunately, there is an easy solution: just install devel/gmake and call it a day (of course, the command to use then is gmake instead of make).

If I remember correctly, the development packages for GTK were already installed, probably as part of GhostBSD*-dev. The only missing piece was pkg-config, which is availed by installing the package devel/pkgconf.

Now that the whole build system is ready, the only thing that can go wrong is the code. And go wrong it did.

Why Try Different Compilers

Fixing Vara’s code for Clang on BSD didn’t require much work. However, it reminded me of the importance of trying out different compilers on the same code.

Getting your code to work with GCC is a must if you are targetting GNU/Linux, and that’s all you need even if you target BSD, macOS, or even Windows, as long as you provide pre-built binaries for all these platforms. But if you want others to be able to freely compile your code with the default compiler for their platform, you need to make sure your code works with at least Clang (and maybe MSVC as well, for many reasons including MinGW64 and MinGW64-generated binaries being incorrectly marked malicious).

One added advantage of trying out different compilers is getting warnings that your favorite compiler missed. For example, if it weren’t for Clang, I wouldn’t have noticed that the #pragma pack(1) that I used in one header for a particular struct was affecting other structs from other headers as well. This helped me switch to enclosing the particular struct with #pragma pack(push, 1) and #pragma pack(pop) instead.

Standards Aren’t Enough

Isn’t enforcing a standard with flags like -std=c99 enough for portability? Not actually. For example, initializing a global variable to sqrt(2) would work fine on GCC, while Clang would complain error: initializer element is not a compile-time constant. What Clang says is valid, and it wasn’t a problem on GCC only because sqrt(2) was being replaced by a precomputed value (M_SQRT2 from glibc’s math.h).

Platforms Matter Too

Trying out different compilers on your development platform is a really good start, but you cannot be sure until you do so on the target platforms. For example, substituting pthread_t with unsigned long (don’t ask me why) would work fine with both GCC and Clang on GNU/Linux (amd64), but not on GhostBSD (amd64). The definition of pthread_t is incompatible with unsigned long there.


Tags: bsd, clang, gcc, vara, c, portability, programming

Read more from Nandakumar at nandakumar.org/blog/