Say What ?

Your GCC build flags. Yes, this is actually an interesting question! I have been building code at the various places where I work for many years, on different architectures, and tweaking the build flags has always been an important task.

I mean, you probably are too experimented to just use something like:

1 gcc -c fubar.c -o fubar.o

And you probably use -W -Wall , or additional flags, to tune the compiler behavior.

The idea behind is not only to carefully optimize produced bytecode, but also to improve its quality, and its security. Compiler warnings are critical to spot many programming errors or lethal typos that would otherwise consume days of debugging (and a big pile of money!).

Many beginners are still using nowadays the default gcc command without further tuning, and this always makes me cringe. Warning messages are not annoying, they are useful. And the minutes you spend fixing them (Note that I did not say hide, but fix) will spare you days or even months of nightmares.

Here are the flags we are using where I work:

1 gcc -pipe -m64 -ansi -fPIC -g -O3 -fno-exceptions -fstack-protector -Wl,-z,relro -Wl,-z,now -fvisibility = hidden -W -Wall -Wno-unused-parameter -Wno-unused-function -Wno-unused-label -Wpointer-arith -Wformat -Wreturn-type -Wsign-compare -Wmultichar -Wformat-nonliteral -Winit-self -Wuninitialized -Wno-deprecated -Wformat-security -Werror -c source.c -o dest.o

Within these fancy flags, I was among the craziest tuner. Here’s the summary of what I shamelessly added:

-pipe I prefer NOT to use temporary files when possible, and use pipes, for example when compiling preprocessed code into intermediate assembly source (.S). This has no real impact on performances (temporary files are generally put on some kind of tmpfs filesystem), but this is a bit cleaner.

-fno-exceptions I don’t like C++ exceptions. And we don’t use them where I work. So let’s remove the overhead generated by unwinding code (ie. code and data aimed to allow the runtime to “rollback” function calls in case an exception is thrown, eg. object destructors that need to be called, etc.). Bonus: it may also produce a bit faster code. What if an exception is thrown (eg. by the runtime or the STL) by the way ? Well, abort() will be called instead, which is perfectly fine as thrown exceptions are either programming errors (out_of_range, bad_cast etc.) or critical conditions ( new throwing bad_alloc)

-fstack-protector -Wl,-z,relro -Wl,-z,now -Wformat-security These flags are actually ripped from the Debian Hardening Wiki, and are aimed to detect stack smashing issues, have a read-only global offset table (preventing any attacks involving writing through the GOT), and various format strings suspicious usages.

-fvisibility=hidden Ah yes, this one is cool. It changes the default extern symbol export mode in GCC to “hidden”. Basically, it means that “extern” symbols are visible by all units within a library, but not outside this library. If you ever wrote code on Windows environments, you probably remember those: _declspec(dllexport) and _declspec(dllimport) . Without them, symbols inside your DLL would not be exported (or imported). On POSIX systems, you export all extern symbols, and they become visible in your .so library. But this default mode has several drawbacks You are exporting internal functions you do not want people to mess with You may have internal symbols conflicting with other libraries You may have troubles having your code building on Windows “out of the box”



The last argument was actually the strongest one: the build would be broken several times per week because someone forgot to properly export the symbols and the code was building fine on Linux but not on Windows. For all these reasons, using -fvisibility=hidden (and properly exporting symbols using __attribute__ ((visibility("default"))) ) was a true relief!

-Wpointer-arith We do not want to know the size of void , and we won’t use it!

-Wformat-nonliteral Banishing “non literal format strings” was actually a sane decision. Especially when the format string source was some kind of user generated input – we’re not in 1990 anymore, and security issues involving string format can not be ignored anymore.

-Winit-self This is actually a joke in the C standard.

1 int i = i ;

Yes, this code is perfectly valid, and won’t even produce a single warning by default. And, of course, the behavior is unspecified — the variable i is left “initialized” with a garbage value. I have been bitten by this idiotic default behavior too often before turning on this specific warning. Shame on me.

-Werror Haha. This one was painful to propagate through all of our code. It basically breaks the build once you have a warning somewhere. Yes, the code won’t build unless it is totally warning-free. Yes, people were a bit upset at me at the beginning :), and they were even more upset when committing code that would break on a new compiler version (but not on an older one). This was painful. This was hard. But I did not give up! And at the end, the overall impact was tremendously positive. Warnings emitted during build are useless unless someone check them regularly (and grep ‘ping thousands of lines of logs is not a cool thing to do, and nobody is going to do that) As more warnings are emitted, and ignored, new warnings tend to be ignored too. As more warnings are emitted, people tend to give up at fixing them (Hey! I’m not the janitor!). Warnings are sometimes annoying, but most of the time they are warnings that you are doing something wrong (possibly really wrong) It is far better to spend five minutes fixing a warning than spending few months on a vicious bug (yes strict aliasing rules, I am talking about you) You can always disable specific warnings ( -Wno-* flags) if needed!

Haha. This one was to propagate through all of our code. It basically breaks the build once you have a warning somewhere. Yes, the code won’t build unless it is totally warning-free. Yes, people were a bit upset at me at the beginning :), and they were even more upset when committing code that would break on a new compiler version (but not on an older one). This was painful. This was hard. But I did not give up! And at the end, the overall impact was positive.

Oh, and on the linker side, I did a bit of tweaking too:

-Wl,-O1 Did you know that you also have an optimization flag for the linker ? Now you know!

-Wl,--discard-all Discard all local symbols, for the sake of library size.

-Wl,--no-undefined Yes, I don’t like missing (unresolved) symbols at link time, even if this is actually a feature. It makes the Windows build easier too, by preventing behavioral differences between Linux/Windows.

-Wl,--build-id=sha1 This adds a fancy “build identifier” in all produced modules, which has a deterministic value (ie. two builds with the same sources and build flags will produce the same identifier), allowing to control whether or not you managed to rebuild from scratch and produce the same binaries.

-rdynamic (note: this actually passes the -export-dynamic flag to ld ) This linker option allows you to have more exported symbols within the dynamic symbol table, which is actually a nice thing when attempting to have readable backtraces!

That’s all, folks.

TL;DR: Take the time to read carefully and understand the man gcc and man ld pages. It is worth it.

Thanks to Andrew Hochhaus for changing -fvisibility=internal to -fvisibility=hidden. Thanks for the insightful remarks to all hacker news contributors, including additional flags and their advantages.