Aniruddha Deb

C development on a Mac

C development on a Mac has it’s fair share of traps and pitfalls, even though Mac is supposed to be POSIX compliant. I’ll cover a few of them here, with the hope that nobody repeats the same mistakes :)

XCode CLT Pitfalls

The first thing one does while starting out with C development is to install a compiler. This is slightly more complicated than it looks on Mac: the simple way is to go the way of installing XCode command line tools, but there’s a few things here to note:

You have to be an apple developer

This means that you can’t have a C/C++ toolchain without being a part of of their developer program. There is a (messy) way of working around this, by instead installing and using GCC with HomeBrew, but I’ll get to that in a minute.

XCode CLT uses the clang toolchain

This is by far the most important pitfall, as XCode purposefully misaliases a lot of stuff. After installing XCode CLT (and provided that you don’t have GCC installed or aliased), try doing a gcc -v or g++ -v. You’d get

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

This is quite misleading, as gcc and clang are fundamentally different compilers. Here’s one example of that. A more accessible example would be this:

int x = 3;
int z = x/++x;

try compiling this with gcc and clang, and be surprised*.

Headers? What Headers?

Headers are also very weirdly located in /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include, and these headers are also not symlinked to /usr/include by the installer. This sucks, and I struggled with making vim autocomplete work because of this reason; vim’s path searches /usr/include and not this weird obscure path. Once I manually included it, it looked horrible and I needed to symlink it to /usr/include worked okayish.


One note is that you could also use the path /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include, as MacOSX.sdk is linked to the latest version of SDK installed. This does not make the location of includes any less weird.

Another thing to note is that other libraries install their headers in /usr/local/include, so this discrepancy is bad. This has also not been fixed in Big Sur: how hard is it to keep a set of headers in /usr/include rather than in this unsightly place! I mean every UNIX system does this! Ugh


CLT also ships with the old ctags rather than exuberant ctags, which is basically useless. Neither is there a simple way to distinguish between the ctags that it ships and exuberant ctags until it fails hard when you go to make your tags file, since they have the same ctags command. The way of fixing this is to install the proper ctags with brew install ctags and then add alias ctags=/usr/local/include/ctags to your .zshrc.

Using GCC

So you decide that the clang toolchain is not working out for you (maybe because it’s slow or for other reasons), and you decide to switch to the gcc toolchain (by Toolchain, I refer to all associated tools, so that’s gcc, g++, gdb etc). Installing the tools are quite easy, and they just happen with brew install gcc gdb. However, gcc and g++ are already taken by clang, so you’ll have to export them in your .zshrc again

alias gcc=/usr/local/bin/gcc-10
alias g++=/usr/local/bin/g++-10

GDB, however, is very tricky to get working right; MacOS due to SIP and Gatekeeper, does not allow running processes to latch on to other processes. For GDB to work, it would need to be signed. For this, you’ll have to sign it yourself with a certificate, and add that certificate to the trusted keychain in your system. I still could not get it to work after this (more info here), and just using sudo was a hack that I used while I tried to get GDB to work. LLDB, however, is much better integrated with debugging, and I eventually shifted over to using LLDB rather than GDB (aided by this resource).

The GCC/G++ aliasing is highly recommended, and you could leave out GDB (which is what I’ve done in my current setup)

Other stuff

make seems to work without a hitch so far, which is a good thing. cscope also had to be installed with brew, and cscope also works well now. I’ll need to finish setting up my vim environment for C autocompletion and play around more with CTags and cscope (maybe write about it in the next post :)) For now, this has unfortunately turned into more of a rant than a how-to, but I still hoped it help some of you avoid these traps and pitfalls.

*: GCC gives the output as 1, while clang gives it as 0