Table of Contents
Compiler
A compiler is responsible for translating code written in a high level language into code in a lower level language. We use the compiler to turn c++ code an executable program.
The compiler we use is the GNU c++ compiler, g++.
Note that on many systems g++ can also be invoked using c++.
This is a very complex piece of software capable of compiling multiple high level programming languages into machine language and assembly language on many different architectures and operating environments. The following is a very simplified overview.
A Note on Examples
In the following discussion, examples of using the compiler at the command line will be given. These examples were produced using bash as the shell.
- Any line starting with $ is a command typed by the user.
- Other lines are output from the program.
In the following session, the user typed the ls command. Note, the shell supplied the $, it was not typed by the user.
$ ls a.out* hello.cpp
Basic Usage
By default the compiler takes a single argument, the name of the file containing code you wish to compile. If the compile is successful, the executable program will be stored in the file a.out.
$ ls hello.cpp $ g++ hello.cpp $ ls a.out* hello.cpp $ ./a.out
In this example:
- The user typed ls to show the files in the current working directory.
- The shell responded by showing hello.cpp was the only file the the directory.
- The user then typed g++ hello.cpp
- The g++ produced no output to the screen.
- This means that the program compiled successfully.
- The user typed ls again.
- The file a.out appeared in the directory.
- This is the executable program produced by compiling hello.cpp
- The user then typed ./a.out to execute the program.
- Any output produced by the program would be displayed next.
Syntax Errors
Syntax errors are produced when the code is not in the proper format. Syntax errors are a side effect of the compiler.
When a compiler encounters a syntax error it will:
- Report the line where the syntax error occurred
- Report the cause of the syntax error.
In addition, the compiler will possibly report additional syntax errors. These errors may or may not be valid. It is always best to fix the first syntax error the compiler reports.
If the following code is compiled:
#include <iostream> using namespace std; int main() { // the second quotation is missing on the next line. cout << "Hello World! << endl; return 0; }
The following syntax errors might be reported:
$ g++ hello.cpp hello.cpp:7:13: warning: missing terminating " character 7 | cout << "Hello World! << endl; | ^ hello.cpp:7:13: error: missing terminating " character 7 | cout << "Hello World! << endl; | ^~~~~~~~~~~~~~~~~~~~~~ hello.cpp: In function 'int main()': hello.cpp:9:5: error: expected primary-expression before 'return' 9 | return 0; | ^~~~~~
The compiler has detected the syntax error on line 7, but also produces a error on line 9. There is no error on line 9. Removing the error on line 7 will remove this incorrectly reported error as well.
Note: The compiler will not produce an executable if a syntax error is encountered.
Warnings
In addition to syntax errors, compilers can also produce warnings. A warning is produced when the compiler discovers a section of code which is syntactically correct, but likely to produce an error when the program is run.
Compiling the following code:
#include <iostream> using namespace std; int main() { cout << "3/0 = " << 3/0 << endl; return 0; }
Might produce the following:
$ g++ divide.cpp divide.cpp: In function 'int main()': divide.cpp:7:25: warning: division by zero [-Wdiv-by-zero] 7 | cout << "3/0 = " << 3/0 << endl; | ~^~
Note that the compiler will still produce an executable.
Good programming practices says you should remove most if not all warnings from your code.
Warnings will depend on the flags passed to the compiler. This is discussed later.
Command Line Arguments
Command line arguments allow the user to control how the compiler operates. These are typed following the command (g++ or c++) and are sometimes called flags.
The first command line argument for g++ that many users encounter is the -o flag. This flag is used to tell the compiler to name the output file something other than a.out. Renaming the output file allows users to identify the use of the executable or possibly the source code for the executable.
The -o flag is accompanied by the name of the output file the user wishes to produce. It is normal to compile the source code hello.cpp into an executable named hello. This is done in the following example:
$ ls hello.cpp $ g++ -o hello hello.cpp $ ls hello* hello.cpp $ ./hello
Note that the output is called hello and not hello.cpp. If the user were to allowed to do this, the source code in hello.cpp would be replaced with the new executable just produced. The current version of g++ will not allow this to happen.
$ g++ -o hello.cpp hello.cpp g++: fatal error: input file 'hello.cpp' is the same as output file compilation terminated.
g++ supports many flags. These include
- Language specific flags
- Warning flags
- Debugging flags
- Optimization flags
- Linking flags
- Information flags
- Many others
On most linux systems man g++ will show documentation for the flags supported by your version of g++.
Some Useful Arguments
- - -version will display the version of g++ you are using.
$ g++ --version g++ (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2) Copyright (C) 2019 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.
- In general, you want your version of g++ to be as close to the version of g++ on the homework standard machine specified by your instructor.
- If your version is not the same, make sure that you compile your code on the homework standard machine specified by your instructor before you submit any code.
- -std controls the version of the c++ language the compiler will use.
- There have been multiple c++ standards over the years, generally named after the year the standard was adopted.
- -std=c++98 supports the 1998 ISO c++ standard
- -std=c++11 supports the 2011 version
- -std=c++14 supports the 2014 version
- -std=c++17 supports the 2017 version
- Consider the following code
#include <iostream> using namespace std; int main() { string greeting="Hello World!"; for(auto & letter: greeting) { cout << letter; } cout << endl; return 0; }
- It will not compile under c++98
$ g++ -std=c++98 auto.cpp auto.cpp: In function 'int main()': auto.cpp:8:16: error: ISO C++ forbids declaration of 'letter' with no type [-fpermissive] 8 | for(auto & letter: greeting) { | ^~~~~~ auto.cpp:8:24: warning: range-based 'for' loops only available with '-std=c++11' or '-std=gnu++11' 8 | for(auto & letter: greeting) { | ^~~~~~~~ auto.cpp:8:24: error: forming reference to reference type 'std::__cxx11::basic_string<char>&'
- But it will compile under c++14
$ g++ -std=c++14 auto.cpp
- There are many other standards supported.
- You should check with your instructor regarding which version of the compiler you should use.
Producing Additional Warnings
Compilers are also capable of analyzing source code to predict where this code may cause run time errors. By turning on these warnings you are more likely to write code that conforms to the standard and less likely to have common runtime errors in your code.
Your instructor may require one or more of the following flags when compiling code:
- -Wpedantic
- This requests the compiler to follow the ISO C++ standard, rejecting forbidden and non-standard extensions
- -Wall
- Enables MANY different warnings for constructs that are considered questionable.
- -Wextra
- Enables many additional warnings
- -Wmisleading-indentation
- Warn if the indentation of the code does not reflect the block structure.
- -Wunused
- Warn if different declared items are not used
- -Wuninitialized
- Warn about uninitialized variables.
- This flag requires -On for some variables.
- See below.
- -Wshadow
- Warn when shadow variables are declared in a scope
- -Wconversion
- Warn when an implicit conversion may change a value.
Note: There are many other warning producing flags.
For the following code:
#include <iostream> using namespace std; int main() { int a,b; int c; if (a = b) cout << " a is 4" << endl; a = 5; return 3.14; }
Compiling without additional flags produces no warnings. But compiling with the listed flags produces
$ g++ bad.cpp $ g++ -g -O3 -Wpedantic -Wall -Wextra -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion bad.cpp bad.cpp: In function 'int main()': bad.cpp:10:11: warning: suggest parentheses around assignment used as truth value [-Wparentheses] 10 | if (a = b) | ~~^~~ bad.cpp:10:5: warning: this 'if' clause does not guard... [-Wmisleading-indentation] 10 | if (a = b) | ^~ bad.cpp:12:8: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if' 12 | a = 5; | ^ bad.cpp:14:12: warning: conversion from 'double' to 'int' changes value from '3.1400000000000001e+0' to '3' [-Wfloat-conversion] 14 | return 3.14; | ^~~~ bad.cpp:8:9: warning: unused variable 'c' [-Wunused-variable] 8 | int c; | ^ bad.cpp:10:11: warning: 'b' is used uninitialized in this function [-Wuninitialized] 10 | if (a = b) | ~~^~~
You should use all command line flags specified by your instructor.
Note: If you wish to avoid typing command line arguments, you may wish to investigate
- Makefiles
- Command Line History
Other Arguments
Other flags are useful in different situations.
- -g tells the compiler to retain debugging information. This is useful when using a symbolic debugger such as gdb.
- This is probably a good option to have enabled.
- -On tells the compiler to optimize the code.
- This may result in a more efficient executable.
- n is the level of optimization
- This is currently an integer between 1 and 3.
- Compiling with higher values of n may take longer, but will generally result in a faster executable.