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
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-1188.8.131.52) Target: x86_64-apple-darwin19.6.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin
This is quite misleading, as
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
clang, and be surprised*.
Headers? What Headers?
Headers are also very weirdly located in
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
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
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
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
(by Toolchain, I refer to all associated tools, so that’s
Installing the tools are quite easy, and they just happen with
brew install gcc gdb. However,
g++ are already taken by
so you’ll have to export them in your
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
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
The GCC/G++ aliasing is highly recommended, and you could leave out GDB (which is what I’ve done in my current setup)
make seems to work without a hitch so far, which is a good thing.
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