The differences between several methods of defining global variables/constants in C/C++

Reprinted from: https://www.cnblogs.com/catkins/p/5270388.html

Summary:

1. Global variables should not be defined in the header file. They can only be declared in the header file. The definition must be in the source file.
2. If you want to use global variables in other files, the safest way is to declare them in common.h. Defined in common.cpp, other files include "common.h" to use global variables
3. The scope of global variables modified by static and const can only be the own compilation unit, so you only want to use global variables in this file, just in Use const or static in the source file to modify
4. Only variables can be declared and initialized in the global domain, but no operations can be performed.

 

Before discussing global variables, we must first understand a few basic concepts:

1. Compilation unit (module):

    Today, with the popularity of IDE development tools, many people are no longer clear about some concepts of compilation. What many programmers fear most is handling link errors (LINK ERROR), because it does not give you program errors like compilation errors. Specific location, you often feel annoyed by this kind of error, but if you often use gcc, makefile and other tools to do development work under Linux or embedded, then you may understand the difference between compilation and connection very well! When you finish writing the code on a development tool like VC and click the compile button to prepare to generate the exe file, VC actually does two steps . The first step is to compile each .cpp (.c) and corresponding .h file into obj file; the second step is to LINK all the obj files in the project to generate the final .exe file. Then errors may occur in two places. One is a compilation error, which is mainly a syntax error, and the other is a connection error. Errors are mainly caused by repeatedly defining variables, etc. What we call a compilation unit refers to each obj file generated during the compilation phase . An obj file is a compilation unit, that is to say, a cpp (.c) and its corresponding .h file together form a compilation unit. The project is composed of many compilation units, and each obj file contains the relative address of variable storage, etc.

2. The difference between declaration and definition
    : When a function or variable is declared, no actual physical memory space is given to it. This can sometimes ensure that your program compiles successfully, but when the function or variable is defined, it is in memory. The actual physical space is limited. If the external variable you reference in the compiled module is not defined anywhere in the entire project, then even if it can be passed during compilation, an error will be reported during linking because the program cannot find it in the memory. to this variable! You can also understand it this way, the same variable or function can be declared multiple times, but the definition can only be once!

3. The role of extern
    extern has two roles . First, when it is used together with "C", such as: extern "C" void fun(int a, int b); it tells the compiler to compile the function fun. When translating the function name, follow the rules of C to translate the corresponding function name instead of C++. The rules of C++ will change the name fun beyond recognition when translating the function name. It may be fun@aBc_int_int#%$ or something else. This depends on the "temper" of the compiler (different compilers adopt different methods). Why do you do this? Because C++ supports function overloading. I will not discuss this issue too much here. If you have If you are interested, you can search online and I believe you can get a satisfactory explanation!
    When extern is not used together with "C" to modify a variable or function, such as in a header file: extern int g_Int; its function is to declare the function or global variable. The scope keyword , the functions and variables it declares can be used in this module or other modules. Remember that it is a declaration and not a definition! In other words, if module B (compilation unit) refers to the module (compilation unit) defined in A For global variables or functions, it only needs to include the header file of module A. During the compilation phase, although module B cannot find the function or variable, it will not report an error. It will generate the target code from module A when connecting. This function is found in .

 If you already understand the above concepts very well, then let us look at the differences in the use of the following global variables/constants:

1. Global variables modified with extern:
The role of extern has been mentioned above. Let’s give an example. For example, there is the following statement in test1.h:

#ifndef TEST1H
#define TEST1H

extern char g_str[]; // 声明全局变量g_str
void fun1();

#endif

In test1.cpp

 #include "test1.h"
    
 char g_str[] = "123456"; // 定义全局变量g_str
    
 void fun1()
 {
     cout << g_str << endl;
 }

The above is the test1 module. Its compilation and connection can pass. If we also have the test2 module and want to use g_str, we only need to quote it in the original file.

#include "test1.h"

void fun2()
{
    cout << g_str << endl;
}

 The above test1 and test2 can be compiled and connected at the same time. If you are interested, you can use ultraEdit to open test1.obj. You can find the string "123456" in it, but you cannot find it in test2.obj. This is because of g_str It is a global variable of the entire project. There is only one copy in the memory . The compilation unit test2.obj does not need to have another copy, otherwise the error of repeated definition will be reported during connection!
    Some people like to put the declaration and definition of global variables in Together, this can prevent forgetting the definition, such as changing the above test1.h to extern char g_str[] = "123456"; // This is equivalent to no extern , and then remove the definition of g_str in test1.cpp, this When you compile and connect the test1 and test2 modules, a connection error will be reported . This is because you placed the definition of the global variable g_str after the header file. The test1.cpp module contains test1.h, so g_str is defined once. And test2.cpp also contains test1.h, so g_str is defined again. At this time, the connector finds two g_str when connecting test1 and test2. If you insist on placing the definition of g_str in test1.h, then remove #include "test1.h" from the code of test2 and replace it with:

extern char g_str[];
void fun2()
{
    cout << g_str << endl;
}

       At this time, the compiler knows that g_str is an external compilation module and will not define it again in this module. But I want to say that this is very bad because you cannot use it in test2.cpp# include "test1.h", then you cannot use other functions declared in test1.h , unless they are also modified with extern. In this case, you will have a long list of functions declared, and the function of the header file is to provide The interface is provided externally, so please remember to only make declarations in the header file. The truth is always that simple .

2. Global variables modified with static:

        First of all, I want to tell you that static and extern are incompatible, which means that extern and static cannot modify a variable at the same time ; secondly, the global variable modified by static is declared and defined at the same time , which means that when you After the global variable is declared using static in the header file, it is also defined at the same time ; finally, the scope of the static modified global variable can only be its own compilation unit , which means that its "global" is only valid for this compilation unit, and other The compilation unit cannot see it , such as test1.h:

#ifndef TEST1H
#define TEST1H

static char g_str[] = "123456"; 
void fun1();

#endif

test1.cpp:

#include "test1.h"

void fun2()
{
    cout << g_str << endl;
}

test2.cpp:

#include "test1.h"
    
void fun2()
{
    cout << g_str << endl;
}

 The above two compilation units can be connected successfully. When you open test1.obj, you can find the string "123456" in it, and you can also find them in test2.obj. The reason why they can be connected successfully without reporting The error of duplicate definition is because although they have the same content, the physical addresses stored are different, just like two different variables are assigned the same value, and these two variables act on their respective compilation units.
       Maybe you are more serious and secretly trace and debug the above code. As a result, you find that the memory address of g_str in the two compilation units (test1, test2) is the same, so you conclude that statically modified variables can also act on other modules, but I I want to tell you that your compiler is deceiving you. Most compilers have optimization functions for the code to achieve the goal of generating a target program that saves memory and has higher execution efficiency. When the compiler connects the various compilation units, Sometimes, it will only copy one copy of the memory with the same content, such as "123456" above. The variables located in the two compilation units have the same content, so only one copy will exist in the memory when connecting. , if you change the above code to the following, you can immediately expose the compiler's lies:

test1.cpp:

#include "test1.h"
    
void fun1()
{
    g_str[0] = 'a';
    cout << g_str << endl;
}

test2.cpp:

#include "test1.h"
    
void fun2()
{
    cout << g_str << endl;
}
void main()
{
    fun1(); // a23456
    fun2(); // 123456
}

When you trace the code at this time, you will find that the g_str address in the two compilation units is not the same. Because you modified it in one place, the compiler was forcibly restored to the original appearance of the memory, and there are two copies in the memory. Copy it to the variables in the two modules. It is precisely because static has the above characteristics that when defining static global variables, it is generally placed in the source file instead of the header file, so as not to cause unnecessary information pollution to other modules . Remember this principle as well. !

3. Global constants modified by const:

Global constants decorated with const are widely used. For example, error message strings in software are defined using global constants. Global constants modified by const have the same characteristics as static , that is, they can only be used in this compiled module , but const can be used with extern to declare that the constant can be used in other compiled modules, such as
extern const char g_str[]; Then don't forget to define it in the original file: const char g_str[] = "123456";

       So when const is used alone, it is the same as static , and when used together with extern, its characteristics are the same as extern ! So I don't have anything to describe const too much. I just want to remind you that const char* g_str = "123456" is different from const char g_str[] = "123465" . The previous const modifies char * instead of g_str, its g_str is not a constant , it is regarded as a defined global variable (can be used by other compilation units), so if you want char *g_str to comply with the rules of const global constants, it is best to define const like this char* const g_str="123456"

Guess you like

Origin blog.csdn.net/SwordArcher/article/details/113512348