captainepoch's log

Using the C preprocessor

We already know that C is a language broadly used in almost every computer or computer-like gadget. We also know that C is a complex thing and thus every operative system has its own POSIX API in order to give some portability (Windows is special, I won’t cover anything about Windows and C here).

So, as a student, I have assignments. I’m also a Computer Science and Engineering student, so some of those assignments are in low level languages (C and C++). I use Mac and GNU/Linux day-to-day and will love using the POSIX API in both operative systems without worring about library paths or some syscalls. So, as far as I know, some library paths are different between Mac and GNU/Linux, and thus we have to put those paths at the #include macro. I’m going to write my findings about using the C preprocessor to solve this issue.

Disclaimer

This is not a professional post but the findings of a student who likes C enough to look at it and say: “I’m going to own you…”

The code of this post is going to be published at my GitHub’s account, so I will only post code snippets (you’ll find a Makefile to compile every code).

Environments

For this post I’m using my MacBook Pro with the latest version of El Capitán and my Raspberry Pi B+ with the latest version of Raspbian.

Mac compiler:

Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin15.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

RPi compiler:

gcc (Raspbian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

What is the Preprocessor?

The C preprocessor, often known as cpp, is a macro processor that is used automatically by the C compiler to transform your program before compilation. It is called a macro processor because it allows you to define macros, which are brief abbreviations for longer constructs.

Source: Overview

The preprocessor does lexical substitutions, which is a hardcore copy and paste from macros to the source code!

Some code!

When we want to define some constant values in C we can use the const or #define. The #define macro is part of the preprocessor of C, so for example (code: ex1.c):

#define STRING "Hello, world"

You’ll get:

Hello, world

The #define macro is widely used (e. g. look at the Linux’s source code).

It’s cool, but there’s no problem with this, it’s easy to use and compatible with both operating systems.

So, for example, you can define macro calls! Let’s call it ‘little functions’. So, for example, you need a comparator (because of… reasons). So, let’s define a macro for that (code: ex2.c):

#define equal(X, Y)  ((X) == (Y) ? (X) : (Y))

It means: if X is equal to Y, returns X, else Y. It’s an if but in a macro! Awesome :3.

And, of course, the output you get if true:

FALSE (2): 2
TRUE (1): 1

But, again, both operating systems have no problem with this, at all, it works cleanly.

As I said at the top of this post I wanted something more hardcore, like distinguish between paths. In an assignments we had to upload for correction we needed to use a lib called ucontext.h. So, why would I want to use the preprocessor because of it? Well…

Linux path:

/usr/include/ucontext.h

Mac path:

/usr/include/sys/ucontext.h

So… We need the preprocessor… Again! :) (code: ex3.c).

Let’s get started seeing what are the macros we need. First of all, it’s neccesary to know the macros for both platforms, which are __APPLE__ for Mac and __linux__ for GNU/Linux.

Of course, there’s an example (code: ex3.c) which prints Mac OS if you compile and run in a Mac and GNU/Linux if you do it in Linux.

You can do it too for other macros (code: ex4.c) which prints 100 if you compile and run in a Mac and 200 if you do it in Linux.

And we went to the last example (code: ex5.c), where different paths for the same lib is needed. So, if you open that file, you see:

#if __APPLE__
#include <sys/ucontext.h>
#elif __linux__
#include <ucontext.h>
#endif

That snippet is choosing between to paths for the same library. If there’s some POSIX love between GNU/Linux and Mac, we should be able to use the same syscall of the same library. So, that means portability, and that’s good! :D


As you can see C is really powerful and it was engineered in order to get portability from one operating system to another. POSIX allows it, and it’s cool. I’ll edit this post to add things if I see more interesting stuff about the preprocessor that I could use and show you. This is bigger than you think, believe me!

You can get more info at GNU GCC’s Preprocessor wiki.


I’m very thankful to @voiser who readed this post and pointed me some typos and grammatical errors that needed to be fixed :).