0% found this document useful (0 votes)
7 views19 pages

Compilation and Execution Program C++

The document provides a comprehensive guide on compiling and executing C++ programs, detailing the processes of pre-processing, compiling, assembling, and linking. It includes practical steps for using the GCC compiler, examples of header files, and the importance of header guards to prevent multiple inclusions. Additionally, it explains how to manage symbols in C++ through custom functions and the use of extern 'C' for name mangling.

Uploaded by

poonam Bhalla
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views19 pages

Compilation and Execution Program C++

The document provides a comprehensive guide on compiling and executing C++ programs, detailing the processes of pre-processing, compiling, assembling, and linking. It includes practical steps for using the GCC compiler, examples of header files, and the importance of header guards to prevent multiple inclusions. Additionally, it explains how to manage symbols in C++ through custom functions and the use of extern 'C' for name mangling.

Uploaded by

poonam Bhalla
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 19

Comilation and execution in c++

While working in C++ programming language, it is important to


know how to compile C++ program codes and how to execute them.
When we write code, we write it in a high-level language that
machines can't understand, known as source code. The code that
machines can understand & execute is in binary form (0's and 1's) and
is known as machine code, object code, or executable code.

Translating source code (high-level language code) into machine-


readable code consists of the following four processes that we will
learn in detail as we move through the course of this article:

1. Pre-processing the source code


2. Compiling the source code
3. Assembling the compiled file
4. Linking the object code file to create an executable file

First, Let us learn how to compile C++ and execute C++ program
code on a terminal or command prompt practically!
Assuming that we have already installed the GCC compiler and we
have the hello.cpp file that we need to compile and execute, we now
need to follow the below instructions:

STEP 1: We need to open the terminal window or command prompt


(if we are working on Windows).

STEP 2: We must now change the location to the directory where


the hello.cpp file exists.

Example: If the hello.cpp file exists in the Documents folder, we


need to run the below command on the command prompt:

// cd command means changing the directory location to the


Documents folder where hello.cpp file exists.
cd 'C:/Users/Dell/Documents'

STEP 3: Now, to compile the hello.cpp source code file, we need to


use the below command:

g++ -o <any_name_for_your_program> hello.cpp

STEP 4: Assuming we have given the name "myProgram1" while


compiling hello.cpp file, now to run & execute the code, we need to
write the below command:

On Terminal:

./myProgram1

While using Windows command prompt:

myProgram1.exe

A C++ compiler translates C++ source code into machine language


code and stores it on the disk with file extension .o (here, hello.o).
The linker then links this object code file with standard library files
required by the program code and, thus, creates an executable file in
the end which is again saved on the disk. While we try to run and
execute the C++ code file, the executable file is loaded from the disk
to the memory, and then the CPU executes the program (one
instruction at a time).

The Build Pipeline: Preprocess, Compile, and Link

As we have already discussed, each C++ source code file needs to be


compiled into an object file first, and then the linker links this object
file into an executable file to execute & run. C++ source code files
can contain various header files with the #include directive.
The header files can have the extension .h, or even no extension is
needed if we are using C++ standard library template.

// Examples of header files used in C++ source code files with the
#include directive.
#include <iostream>
#include <bits/stdc++.h>

The first step while translating the source code into machine-readable
code is to pre-process the source code file. The header files included
within the source code file are not passed directly to the compiler;
only the main source code file is passed for pre-processing and
compilation purposes. Header files are then indirectly included from
the source file itself, where the C++ pre-processor replaces the lines
containing the #include directive with the actual content of the
included header files.

When we want to compile multiple source code files, header files can
be opened multiple times during the pre-processing phase, depending
upon how many source code files include them. However, source
code files are opened only once for pre-processing and compilation.

The other functionality of the pre-processor is to remove the code


from the source code file upon finding the conditional compilation
blocks (like if-else statements) whose value evaluates to false and is
not needed in retrieving results from the code.

Pre-processor also helps in macro replacements. Macros in C++


usually start with the #define directive, and whenever the compiler
encounters a macro name, it replaces the name with the definition of
the Macro.

#include <bits/stdc++.h> // Header file with '#include' directive.


#define ll long long // Macro where macro-name is ll, which will be
replaced throughout the code by long long during the pre-processing
phase.
using namespace std;

int main() {
ll num = 0; // long long num = 0;
cout << "Value of num: " << num; // Result:- Value of num: 0.
}

After pre-processing phase gets over, we get a translated unit of code


(sometimes huge, depending upon the header files' content).

Example 1. Assuming we have a source code file as hello.cpp and the


below C++ code is present, we are now interested in obtaining
translated unit (pre-processed source code file).

// hello.cpp file containing some C++ code ready for pre-processing.


#include <bits/stdc++.h> // Header file inclusion.
using namespace std;

int main() {
cout << "hello" << endl; // prints "hello"
return 0;
}

To obtain the pre-processed file of the above C++ source code file, we
need to open a command prompt window in the directory location
where the hello.cpp file exists. After that, we need to run the below
command:

g++ -E hello.cpp -o hello.ii // Command to pre-process .cpp file.

Here, hello.ii is the name given to the pre-processed file that will be
obtained.
To check the number of lines present in the pre-processed file, we can
run the below command:

wc -l hello.ii // Command to check the number of lines in the pre-


processed file (hello.ii here).

Output: As we can observe, the pre-processed file hello.ii contains


93,675 lines of code in the machine after including the header file
content.

93675 hello.ii // 93675 lines are present in the pre-processed file

Note that the pre-processed file becomes bigger as we keep including


header files in the source code. After pre-processing, the compiler
starts the compilation phase to produce an object file with an
extension .o (Compiler has to compile a much larger file, i.e.,
translated unit compared to a short & simple source file).

How do Source Files Import and Export Symbols?

Have you ever wondered if we want to manually import and export


custom functions instead of using default header file libraries?

This is possible while working in C++, where source files can import
and export symbols (like custom function names, etc.)

Example: In this example, we created a C++ source code file


named sum.cpp containing two export functions: Int_Sum for adding
two integer values and Float_Sum for adding two float values.

// sum.cpp file containing two functions ready to be exported.

// Int_Sum function to calculate and return the sum of integer values x


and y.
int Int_Sum(int x, int y){
return x + y;
}

// Float_Sum function to calculate and return the sum of float values x


and y.
float Float_Sum(float x, float y){
return x + y;
}

Now, we will compile the above source code file to obtain an object
file named sum.o using the below command:

g++ -c sum.cpp // For compiling & obtaining object file with


extension .o

After generating the object file sum.o, we can now check for the
symbols being exported or imported using the nm command, which
displays information about the symbols in the specified file (which
can be an object file or an executable file).

nm sum.o // For displaying mangled imported & exported symbols.

Output:

Note that only useful information has been displayed in the output.

00000000 t .text
00000000 T __Z7Int_Sumii // Exported symbol
0000000d T __Z9Float_Sumff // Exported symbol

We observe from the output that no symbol has been imported, but
two symbols are exported: Z7Int_Sumii and Z9Float_Sumff as a part
of the text segment (denoted by T), which shows they are function
names.

Note that the original function names, i.e., Int_Sum and Float_Sum,
are being (translated or changed) and if we want to see the demangled
(original) function names as exported symbols. We can use the -
C option and the nm command.

nm -C sum.o // For displaying demangled imported & exported


symbols.

Output: Int_Sum and Float_Sum function names are displayed as the


exported symbols and parameters.
00000000 t .text
00000000 T Int_Sum(int, int)
0000000d T Float_Sum(float, float)

To import and call the above function names, we need to declare them
first. The best way is to create a header file that declares the function
names and by which we can include this header file in the source file
where we want to call the required function names.

We have created a sum.h header file to declare function names:


Int_Sum and Float_Sum.

// sum.h header file.


extern "C"{
int Int_Sum(int x, int y);
float Float_Sum(float x, float y);
}

Now let us create another source code file named output.cpp where
we will use the "sum.h" header file to call function names: Int_Sum
and Float_Sum.

// output.cpp file.
#include <iostream> // Header file inclusion
#include "sum.h" // Importing sum.h header file to call Int_Sum,
Float_Sum functions.
using namespace std;

extern "C"
void printSumInt(int a, int b) {
cout << a << " + " << b << ": " << Int_Sum(a, b) << endl; // Calling
Int_Sum function provided by the sum.h header file to calculate the
sum of integer values a and b.
}

extern "C"
void printSumFloat(float a, float b) {
cout << a << " + " << b << ": " << Float_Sum(a, b) << endl;
// Calling Float_Sum function provided by the sum.h header file to
calculate the sum of float values a and b.
}

As we have discussed above also, C++ mangles (changes or


translates) function names, but that is not the case when we declare
function names using the extern "C". Let us find out that by compiling
the source code file output.cpp and then using the nm command to
display symbols being exported and imported.

$ g++ -c output.cpp
$ nm output.o // For displaying mangled imported & exported
symbols.

Output:

Note that only useful information has been displayed in the output.

As the function names are not mangled due to the use of extern "C",
we had to use different function names for
printing: printSumInt and printSumFloat so that they seem different
from each other while exporting.

U _Float_Sum
U _Int_Sum
00000084 T _printSumFloat
00000000 T _printSumInt

Till now, we have only compiled the source code file into an object
file but have yet to link them. Without linking the object files, the
linker will stop with a "missing symbol" error.

We have created an output.hpp header file (.hpp files can be imported


in both C and C++) to declare printing
functions: printSumInt and printSumFloat.

//output.hpp header file


extern "C" {
void printSumInt(int a, int b);
void printSumFloat(float a, float b);
}

To get the results by calling appropriate function names, we have to


link all the files with another created result.cpp file.

#include "output.hpp" // Importing output.hpp file to use printSumInt


and printSumFloat functions.

int main(){
printSumInt(1, 2); // Prints: 1 + 2: 3 by calling printSumInt
function.
printSumFloat(1.5f, 2.5f); // Prints: 1.5 + 2.5: 4.0 by calling
printSumFloat function.
return 0;
}

To generate the object file result.o for the source code


file result.cpp and to check for imported & exported symbols, we
have to run the below commands:

$ g++ -c result.cpp
$ nm result.o // For displaying mangled imported & exported
symbols.

Output:

Note that only useful information in the output has been displayed
here. As we can observe in the output, the main function has been
exported while the printSumFloat and printSumInt functions have
been imported.

Main function name has not been mangled despite not using
extern "C", just because it is treated as a special implementation-
defined function in C++.

00000000 T _main
U _printSumFloat
U _printSumInt
To link all the object files together to generate an executable file, we
will use C++ linker (g++) and run the below command:

g++ -o output_generated sum.o output.o result.o

Here, output_generated is the name given to the executable file after


linking all the object files: sum.o, output.o, and result.o.

Final Output: (After executing output_generated file)

1 + 2: 3
1.5 + 2.5: 4

How Header Guards Work?

Have you ever wondered what will happen if we use the same header
file multiple times directly or indirectly for a single source code file?
As we know, while pre-processing and compilation, the header
file #include* directive is replaced by the actual header file content,
and using the same header file multiple times, will only result in
duplicated declarations.

Example 1. In this example, we have created


an unguarded.hpp header file with the student class. getNum() and
setNum() methods have been declared & initialized to return and set
the value of private data member "num".

// unguarded.hpp file.
// Class student with some methods inside it.
class student{
private: int num;
public:
student(){
num = 0;
}
// Setter method to set the value of private data member 'num'
with value 'a'.
void setNum(int a){
num = a;
}
// Getter method to get & return the value of private data
member 'num'.
int getNum(){
return num;
}
};

We now have created another header file, guarded.hpp, with the same
file content as in the unguarded.hpp header file, but the difference is
that we have wrapped the entire header file content here within a
conditional block. For the first time while pre-processing, the pre-
processor will include this header file upon checking the (#ifndef)
condition and define the __GUARDED_HPP macro. Next time if the
same source file asks to include this header file again, as the
__GUARDED_HPP macro has already been defined, the pre-
processor will discard the code placed between the #ifndef and the
#endif directives.

// guarded.hpp file.
#ifndef __GUARDED_HPP // If not defined (condition)
#define __GUARDED_HPP // Define

class student{
private: int num;
public:
student(){
num = 0;
}
void setNum(int a){
num = a;
}
int getNum(){
return num;
}
};
#endif
Note that the header file can only be included once for every source
file to avoid duplicated declarations.

To verify the results, we have created a source code file


named source.cpp and have included the guarded.hpp header file two
times.

//source.cpp file
#include "guarded.hpp" // Importing guarded.hpp file.
#include "guarded.hpp" // Importing guarded.hpp file again, but this
has no effect due to the conditional block inside the guarded.hpp file.
#include <iostream> // Including header file.

using namespace std;

int main(){
student st; // st object of the student class.
st.setNum(5); // Setting private data member 'num' of student class
with value 5.
cout << st.getNum(); // prints 5 after calling getNum() function.
}

For pre-processing the source.cpp file, we have to run the below


command:

$ g++ -E source.cpp // For pre-processing source.cpp file.

Note that the pre-processed file will contain only one student class
declaration; hence, we can compile the source.cpp file without any
problem. For compilation, we can run the below command:

$ g++ -o main1 source.cpp

We will verify the results of including the unguarded.hpp header file


multiple times in the newly created source2.cpp file in the code
example below.

// source2.cpp file
#include "unguarded.hpp"
#include "unguarded.hpp" // Including this header file for the second
time will result in a compilation error (re-defining student class), and
hence, it denotes that header guards are important to include
#include <iostream>
using namespace std;

int main(){
student st;
st.setNum(5);
cout << st.getNum();
}

For pre-processing of the source2.cpp file, we have to run the below


command:

$ g++ -E source2.cpp // For pre-processing source2.cpp file.

Note that the pre-processed file will contain two definitions


(duplicated) of the student class after pre-processing. This will result
in an error during the compilation phase. For compilation, we can run
the below command:

$ g++ -o main2 source2.cpp

Output: As we can observe in the output, the pre-processed file


couldn't be compiled due to multiple declarations of the student class.

// Error in compilation
In file included from source2.cpp:2:0:
unguarded.hpp:1:7: error: redefinition of 'class student'
class student{
^~~~~~~
In file included from source2.cpp:1:0:
unguarded.hpp:1:7: error: previous definition of 'class student'
class student{
^~~~~~~

Pass by Value and Constness of Parameters


Note that we have used some const parameters in the below C++
example code, which means we can't change their values within the
function body. If we try to change the values of const parameters, it
will result in a compilation error.

All the parameters passed into the function are passed by value, which
means a copy of the actual variables is passed. If we try to change
these passed by value variables, we only modify the copy within the
function, not the original content.

#include <bits/stdc++.h>
using namespace std;

int sum(int x, const int y){


const int c = x + y;
++x;
// ++y; will result in an error due to the constness of y.
return c;
}

// const float variable 'a', which can't be modified, passed by its value.
float sum(const float a, float b) {
return a + b;
}

// Return the sum of all the elements of vector v using accumulate()


C++ STL function.
int sum(vector<int> v){
return accumulate(v.begin(), v.end(), 0);
}

// const vector of float type passed as parameter.


float sum(const vector<float> v){
// Return the sum of all the elements of vector v using accumulate()
C++ STL function.
return accumulate(v.begin(), v.end(), 0.0f);
}
To obtain the object file and check the imported & exported symbols,
we can run the below commands:

$ g++ -c 123.cpp // For obtaining object file.


$ nm -C 123.o // For displaying demangled imported & exported
symbols.

Output: Note that only the useful information symbols have been
displayed here in the output.

0000001a T sum(float, float)


00000000 T sum(int, int)
00000058 T sum(std::vector<float, std::allocator<float> >)
00000025 T sum(std::vector<int, std::allocator<int> >)

Pass by Reference

While passing variables by reference (&), the constness of variables


matters, and when passed as const, it denotes that the variables can't
be modified within the functional block.

In the below C++ example code where we have used the first sum
function, we have passed const variable 'a' and variable 'b' by
reference. This shows that variable 'a' can't be modified, but we can
modify variable 'b', whose value will reflect in the main function.

#include <bits/stdc++.h>
using namespace std;

int sum(const int&a, int &b){ //a and b are reference variables.
const int c = a + b;
++b; // Modifies the caller variable b originally & not only its
copy.
// ++a; // Error due to constness.
return c;
}

float sum(float &a, const float &b){


return a + b;
}

int sum(const vector<int>& v){


// Return the sum of all the elements of vector v using accumulate()
C++ STL function.
return accumulate(v.begin(), v.end(), 0);
}

For pre-processing and checking the imported & exported symbols,


we can run the below commands:

$ g++ -c 234.cpp
$ nm -C 234.o // For displaying demangled imported & exported
symbols.

Output: As we can notice in the output, symbols are being exported


with their constness.

00000027 T sum(float&, float const&)


00000000 T sum(int const&, int&)
00000038 T sum(std::vector<int, std::allocator<int> > const&)

Pass by Pointer

 If we want to declare a pointer to a const element, it will be


represented in either of the following ways:

int const *
const int *

 If we want to declare the pointer itself to be const i.e., we can't


change the pointer to point to something else, then it can be
done using the following way:

int const * const


const int * const

 If we want to make just the pointer itself to be const, but not its
pointing element, we can do so in the following way:
int * const

In the below C++ example code, we have passed a pointer to the


vector arr where we can't change (clear/erase) the content of the
vector due to its constancy. The pointer to the vector itself is not
const, and hence, we can point it to a new location without any error.

#include <bits/stdc++.h>
using namespace std;

int sum(const vector<int>* arr) { // Pointer to the vector named arr,


which is specified as const.
// arr->clear(); //can't modify the const object pointed by arr.
const int sum = accumulate(arr->begin(), arr->end(), 0); // Sum of
all the values of vector.
arr = NULL; // Making arr point to NULL.
//sum++; // Not allowed due to the constness of sum.
return sum;
}
$ g++ -c pointer.cpp
$ nm -C pointer.o // For displaying demangled imported & exported
symbols.

Output: When we pass by a pointer, we are using reference, and the


only difference is that when we pass by reference, we pass the actual
element's reference (not pointing to NULL), while a pointer can also
point to NULL. As we can see in the output, the constness of the
pointer is also being exported, and it denotes whether we can modify
the element pointed by the pointer or not.

00000000 T sum(std::vector<int, std::allocator<int> > const*)

Compiling with Different Flags

The most common compiler flags in C++ are as follows:

 std -> It specifies the C++ version or ISO standard version.


For example: -std=c++ 17 (ISO C++ 17) and -std=gnu++ (ISO C++
with gnu extensions)

 Verbosity [W stands for warning]

1. -Wall -> It turns on mostly all the compiler warning flags (-


Waddress, -Wcomment etc.)

2. -Werror -> It turns any warning into a compilation error.

3. -Wextra (-W) -> It turns on extra remaining compiler


warning flags not turned on by -Wall flag such as -Wsign-
compare, -Wtype-limits, etc.

4. -Wpendantic -> It issues all the warnings required by ISO


C++ standard.

 -o -> We can use it to get output of a C++ file.

For example: g++ file.cpp -o hello.bin

 Compilation flags -D

1. -DCOMPILE_VAR -> It enables COMPILE_VAR flag and


is equivalent to add to the code (#define COMPILE_VAR).

2. -DDO_SOMETHING=1 -> It is equivalent to adding to the


code (#define DO_SOMETHING=1).

Conclusion

 Translating the source code (high-level language code) into


machine-readable code consists of the following four processes:

1. Pre-processing the source code


2. Compiling the source code
3. Assembling the compiled file
4. Linking the object code file to create an executable file

 To obtain an object file with an extension .o from a source code


file (say, code.cpp), we can run the below command:
g++ -c code.cpp // For obtaining object files.

 How to compile C++? To answer how to compile C++ (say


hello.cpp file), we can run the below command in the command
prompt:
// Command for compiling hello.cpp file & obtaining its object file.
g++ -o <any_name_for_your_program> hello.cpp

 For pre-processing a source code file (say, hello.cpp), we can


run the below command:
g++ -E hello.cpp // For pre-processing hello.cpp file.

 To check the imported and exported symbols in a pre-processed


file (say hello.cpp), we can run the below command:
nm hello.cpp // For displaying mangled imported & exported
symbols.
nm -C hello.cpp // For displaying demangled imported & exported
symbols.

You might also like