10 Flags and Parameters Tips for GNU GCC

The main parameters of GNU GCC


10 Flags and Parameters Tips for GNU GCC


The GNU GCC is a collection of compilers that was initially created only for the C language and the acronym meant GNU C Compiler, but over time several other programming languages were added, such as: C++, Objective-C, Objective-C++, Fortran, Ada, D, Go, Rust and Java. And the meaning of the acronym became: GNU Compiler Collection.


How to install GNU GCC

For Windows specifically, you can also directly access this link: How to Install GCC/G++ MinGW on Windows and on any other system just use their package manager.

All the links mentioned here I will leave in the description of this video.

It is also necessary to remember that almost 100% of the flags and parameters used by GCC also work with the clang and clang++ compilers. They’ve already done this on purpose for compatibility, in case companies want to migrate in the future.

The difference between flag and parameter is that the flag influences the binary to be generated, the parameter does not, that is, it is used for programs already built. A compiler can have both (because it is also a program, but it builds binaries), whereas a program only uses parameters.

Now let’s get to know 10 most used GNU GCC flags and parameters!


01. Basic compilation

First of all check your compiler version with g++ --version or gcc --version, some things might not work if your version is too old.

In a basic C/C++ code to compile just use the command: gcc for C language and g++ for C++. Examples:

main.cpp

#include <iostream>

int main(int argc, char **argv){
   std::cout << "Hello World!\n";
   return 0;
}

main.c

#include <stdio.h>

int main( int argc , char **argv ){
   printf("Hello World!\n");
   return 0;
}

Some alternative commands to gcc/g++ can be: basic mode, alias or symbolic link are:

cc # gcc
c++ # g++

To compile, run:

g++ main.cpp # C++
gcc main.c # C

In fact, the cc command was the standard C compiler for Unix, but the source code was not available.


02. Using the -o parameter

If you want to save to a different binary name, use the -o parameter:

Let’s use g++ as a base, but use gcc if you want to achieve the same result.

g++ main.cpp -o myprogram

03. Enabling warnings and errors

The -Wall flag means Warnig all. When there is a flag starting with W that means it is to detect warnings, in this case for all/all, actually a set of flags, there are more than 40 .

Suppose you created a variable and didn’t use it anywhere in your code. If you compile normally, this will pass, but if you use -Wall, the compiler will warn you:

#include <iostream>

int main(int argc, char **argv){
   int x;
   std::cout << "Hello World!\n";
   return 0;
}

g++ -Wall main.cpp

Even so the program compiles, it’s just a warning. If you want to turn this warning into an error, i.e., not compile if there is a warning, you need to combine this flag with another flag which is -Werro, it treats the warning as an error:

g++ -Wall -Werror main.cpp

That is, if you remove the binary and recompile, note that the binary will not be built.

rm a.out

The -g parameter is used for debugging at the operating system level. It’s always good to use it in conjunction with -Wall and -Werror.


04. Extra checks for warnings/warnings

If we use this variable, note that compilation will pass even with -Wall and -Werror:

#include <iostream>

int main(int argc, char **argv){
   int x = 9;
   std::cout << "The number x is: " << x << '\n';
   return 0;
}

But notice that we have the function arguments being passed but not being used. To stop this we can use the flag: -Wextra, but it needs to be together with -Wall and -Werror:

g++ -Wall -Werror -Wextra main.cpp

Either we use these parameters or we need to remove them.

Formerly there was a bug in the compilers that even if you removed it, it was possible to obtain parameters via the command line, for that there was the technique of using the void parameter, but this is no longer necessary:

#include <iostream>

int main(void){ // Like it or not, compile with -Wextra
   int x = 9;
   std::cout << "The number x is: " << x << '\n';
   return 0;
}

05. Be pedantic

According to the dictionary, pedantic is one who cares excessively about small details or rules and the name justifies it.

That is, the -pedantic parameter checks if your code complies with ISO rules, for example:

#include <iostream>

int main(int argc, char** argv){
   int x; // REMOVE the initialization of the variable 'x'
   std::cout << "The number x is: " << x << '\n';
   return 0;
}

When compiling, note:

g++ -Wall -pedantic basic.cpp

Remembering that -pendatic-errors does not enable as an error, it is like an alias for -pedantic itself.


06. Being verbose

You can see all the compiler steps using the --verbose or -v parameter

g++ --verbose -Wall -pedantic-errors main.cpp
g++ --verbose main.cpp

This is usually enabled by default on systems that compile from scratch, like Gentoo, for example, which is where I’m at.


07. Disinfectant for memory

Sanitizers was developed by Google and is such well-made software that it has been natively adopted by the GNU GCC: https://github.com/google/sanitizers.

It checks for memory violations and as its name suggests: disinfects!

Suppose we have this code:

#include <iostream>

typedef struct Illegal{
   std::string name = "GCC";
} illegal;

int main(int argc, char** argv){
   illegal * z = new illegal;
   delete z;
   std::cout << z->name << '\n';
   return 0;
}

If we compile normally and run it several times, note that nothing will be displayed, but in one of the executions it will present the bug:

g++ main.cpp
./.a.out
./.a.out
./.a.out
./.a.out

Now if we compile with Sanitize:

g++ -fsanitize=address main.cpp

Note that running the program will not run and will give you details of the addressing violation:

AddressSanitizer:DEADLYSIGNAL
========================================================== ===============
==22886==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000006 (pc 0x7f357aaac113 bp 0x000000000a68 sp 0x7ffd3f1844e0 T0)
==22886==The signal is caused by a READ memory access.
==22886==Hint: address points to the zero page.
     #0 0x7f357aaac113 in _IO_default_xsputn (/lib64/libc.so.6+0x7d113)
     #1 0x7f357aaaa214 in _IO_file_xsputn (/lib64/libc.so.6+0x7b214)
     #2 0x7f357aa9e9e4 in fwrite (/lib64/libc.so.6+0x6f9e4)
     #3 0x7f357b068ff3 (/usr/lib/gcc/x86_64-pc-linux-gnu/13/libasan.so.8+0x68ff3)
     #4 0x7f357ad3fa34 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) (/usr/lib/gcc/x86_64-pc-linux-gnu/13/libstdc++.so.6+0x13fa34)
     #5 0x55e42bd2b35b in main (/home/marcos/a.out+0x235b)
     #6 0x7f357aa52709 (/lib64/libc.so.6+0x23709)
     #7 0x7f357aa527c4 in __libc_start_main (/lib64/libc.so.6+0x237c4)
     #8 0x55e42bd2b230 in _start (/home/marcos/a.out+0x2230)

AddressSanitizer cannot provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib64/libc.so.6+0x7d113) in _IO_default_xsputn
==22886==ABORTING

Observation: Note that the size of the binary grows a lot when using it, that way you understand why some programming languages that call themselves safe memory have such large binaries, because they already have all these flags by default activated in the compiler . I particularly think this is bad, because the programmer doesn’t know what he’s doing. But, if you want you can create a Bash function in your ~/.bashrc and add all parameters to gcc/g++ automatically, including static analyzers and other tools, but remember your binaries will grow significantly!

vim ~/.bashrc

compile(){
   cppcheck --suppress=missingIncludeSystem $@
   /usr/bin/g++ -Wall -Werror -Wextra -pedantic -g -fsanitize=address -O2 $@
}
alias g++="compile $@"
# :wq

source ~/.bashrc

Or

cat << EOF >> ~/.bashrc
compile(){
   cppcheck --suppress=missingIncludeSystem $@
   /usr/bin/g++ -Wall -Werror -Wextra -pedantic -g -fsanitize=address -O2 $@
}
alias g++="compile $@"
EOF
exec $SHELL

08. Separating header inclusion

In the GTKmm series we use the -MMD parameter. It serves to separate the headers and include them later in the linker.

First of all let’s know what the linker is about. It is a program that merges two binary files together.

Suppose we have this code that needs to compile 2 .cpp files:

// class.hpp
#pragma once
#include <iostream>

class TestLinker {
   std::string hello;
   public:
     TestLinker(const std::string&);
     void get_hello();
};


// class.cpp
#include "class.hpp"

TestLinker::TestLinker(const std::string& str){
   this->hello = str;
   get_hello();
}

void TestLinker::get_hello(){
   std::cout << this->hello << '\n';
}

// main.cpp
#include "class.hpp"

int main(int argc, char **argv){
   TestLinker t("Testing the linker!");
   return 0;
}

We could compile like this directly: g++ main.cpp class.cpp.

But, using the linker (in GCC the linker can use gcc/g++ itself) we can separate the headers and optimize our compilation:

g++ -c main.cpp -o main.o
g++ -c class.cpp -o class.o
g++ main.o class.o
./a.out

However, when there are many headers, it can be duplicated several times to include them, so the -MMD parameter is used.

To make it easy to understand, let’s use the GTKmm series project that is at: https://github.com/terroo/pass-firefox

Let’s clone, check the Makefile and modify.

git clone --recursive https://github.com/terroo/pass-firefox
cd pass-firefox
vim Makefile
# Note that it removes the `.*d`, let's change it to
# we can see this file.
make up
cat main.d

That is, the headers are in a separate file to be included later by the linker.


09. View the generated assembly code

C/C++ compilers create assembly code and then compile assembly code. That’s why the new programming languages are seen as a front-end for C/C++, more precisely the C++ that LLVM is written on.

There are several assembly compilers, and GNU GCC has its own which is the GNU Assembler (an alternative to NASM), and uses the as command.

To generate assembly code:

g++ -S main.cpp

View the generated assembly code:

cat main.s

Use compile as to generate the binary:

as main.s -o main.o

Now just compile main.o

g++ main.o
./a.out

10. Optimizing the binary

We’ve talked about situations where the size of the binary can get quite large depending on the flags you pass in, but this can be improved by using the -O2 flag, actually you can use several numbers: -O1, - O2, -O3, …. but above 2 it’s dangerous depending on the machine you’re compiling, simple machines (2 processors and 8GB of RAM) it’s better to use a maximum of -O2:

Test code:

#include <iostream>

class Test {
   public:
   const std::string str = "Content";
};

int main(int argc, char **argv){
   Test t;
   std::cout << t.str << '\n';
   return 0;
}
g++ main.cpp
du -lhs a.out

g++ -O2 main.cpp
du -lhs a.out

g++ -fsanitize=address main.cpp
du -lhs a.out

g++ -O2 -fsanitize=address main.cpp
du -lhs a.out

The GNU GCC is huge, it has a world of flags and parameters, for everyone use the manual:

man g++
man gcc
g++ --help
gcc --help

Usefullinks


gnu gcc cpp clanguage


Share


YouTube channel

Subscribe


Marcos Oliveira

Marcos Oliveira

Software developer
https://github.com/terroo

Related articles