Original address: thatonegamedev.com/cpp/c-cpp-c…
By thatonegamedev.com/
Post time: 2021-01-02
The compilation process for C and C++ projects and applications is somewhat different from most other languages. First, for those of you who don’t know, there are two types of programming languages — interpreted and compiled.
Interpreted languages (that is, scripting languages) are evaluated at run time. This is slower because the computer doesn’t understand text — it understands certain commands and computations. This means that the text language we write must first be read and translated into computer commands and calculations that are then handed over to the CPU for execution.
The compiled language produces some kind of intermediate bytecode (like instructions in assembly). Assembly is a processor language — it consists of commands and memory management. It is inconvenient to write directly in assembly language, so the higher level of abstraction is achieved by compiling human-understandable text into computer commands.
How does the C/C++ compiler work
In this article, I won’t directly talk about how each compiler handles this process, so it will be purely theoretical. Compilers for other languages are easier to work with because they will more easily do what you expect. The C compilation process is divided into two parts – compilation and linking. The compilation process will take a file and produce the CPU instructions for that file.
But when you’re making an application, you rarely have a single file, so you have to combine (link) all the compiled files into one executable file. This is why the second part is called the linker.
If I understand you correctly…
It’s easy to make mistakes if you don’t fully understand. In every line of C/C++ code, if you execute something, the single file that will be compiled needs to know it exists. For example, if you’ve read my introduction to C++ article, you know that a function can have a declaration that defines its signature and a definition that describes the actual calculation that the function performs. The signature of a function includes what the name of the function is, what it returns, and the types of arguments it accepts. In fact, not only functions can be declared and used independently, but also global variables, structures, classes, and so on. All of these together are also called symbols. A symbol must be declared before it can be used.
If we look at the introduction example of C++, you can put the function definitions in a separate file. But in order for the file that uses the function to know that it exists, you need to add a declaration to the file that uses the function. This has to be done because the files are compiled separately. This is how any symbol type works, and it’s one of the most common mistakes.
The second thing you need to think about is the linking process. You can have a file compiled using only function declarations. It produces machine code (assembly) that references the execution of this function, but it’s like an empty space that references something it doesn’t know exactly. The linker’s job is to find the symbol definition in all the compiled files and use it. There are two common errors here — the sign is undefined and the sign is defined multiple times. You must be careful to avoid both errors, because your program will not be able to produce executable files or libraries.
Header files and using external libraries
When an external library is compiled for use in your own project, you link the output files of the library’s compilation and linking process. But to compile your program to use the library’s functions, you need to add declarations of the library’s functions to your code. In fact, even if you have multiple files in your own code, you can add a function declaration to every.c or.cpp file you write.
This is the repetitive and difficult way. The language provides a way to automatically copy and paste the contents of files for you using the #include ”
” statement. You just write out the path to a file, and the compiler reads the file, copies its contents, and replaces the include statement. The most common names for these files are header files. Headers are files, usually with the extension.h or.hpp, that are used to simplify the declaration of symbols. Header files should only contain declarations, because if you have definitions in them, and they are contained in two translation units (two.c or.cpp files), you will end up with an error where the symbol is defined multiple times.
A symbol undefined type error occurs when you declare a symbol and use it (no problem if you have a declaration and don’t use it), but there is no actual implementation or definition of the symbol in the library file or in any translation unit compiled.
Head protection
Another common mistake is that headers can be re-included. You can include a header that contains another header, and eventually repeat the declaration in your file. That’s why the head guard exists. Header protection should cover all code in your header file like this.
// ExampleHeader.h
#ifndef SOME_UNIQUE_NAME_HERE
#define SOME_UNIQUE_NAME_HERE
// your declarations (and certain types of definitions) here
#endif
Copy the code
This is at least the safest way to compile headers. Some but not all compilers also support another type of header protection called #pragma once. It is placed at the top of the header file.
// ExampleHeader.h
#pramga once
// your declarations (and certain types of definitions) here
Copy the code
Interconnect classes in C++
In C++, you can also have two classes coupled together. In a game, for example, you can have an entity that has multiple components — it needs to update them, query them, add them, remove them, and so on. But components should also know the entities that own them — because they can call methods to find other components that the entities own. To do this, both classes have to know about each other, and you can only really do this cleanly if you have multiple files.
// Entity.h
#pragma once
# We need forward declaration
# this declaration only states the existance of the class
class Component;
class Entity {
public:
std::vector<Component> Components;
}
Copy the code
// Component.h
#pragma once
// There is no problem to directly include this here
// But you cannot have both file include each other as it will loop around itself
// as the compiler would try to endlessly include both files.
#include "Entity.h"
// You can expand the declaration of the component class
class Component {
/ /... some component stuff...
}
Copy the code
In the actual compiled implementation file, you can include both header files, and when the function is defined, they will have full knowledge of both classes.
conclusion
That’s all about the compilation process. In other compiled languages, such as C# or Java, this is reduced to not including the other files, but the symbols within them, which solves the most common error. In C++, it is a little more difficult, but also more powerful. When using external libraries, they provide their own headers, which declare all the functions, and you only need to include them in your files. If you are writing a library for other people to use, remember to keep all publicly used functions separate in a common header file that will be distributed with the compiled library.
www.deepl.com translation