본문 바로가기

Programming/C++ Basic

[Basic C++] 19 - Static and Const keywords in C++

The "static" and "const" keywords are fairly useful keywords when brought in the right place. When we create a variable in a certain scope, it only exists within the scope. On the other hand, a global variable is created at the beginning of a program and destroyed when the program ends.

 

"static" keyword changes the behavior of a local variable to a global variable, thus, the static local variable is created at the beginning of a program and continues to exist even after its scope.


1. Global variable and local variable

The following example well shows the difference between the global variable "g_val" and the local variable "val_x" in the "function_x". 

Code example 1

#include <iostream>
#include <string>
using String = std::string;

// Global variable
int g_val = 0;
int function_g() {
	return ++g_val;
}

int function_x(){
	// Local variable
	int val_x = 0;
	return ++val_x;
}

int main(){
	std::cout << "The value of val_x = " << function_x() << std::endl;
	std::cout << "The value of val_x = " << function_x() << std::endl;
	std::cout << "The value of val_x = " << function_x() << std::endl;

	std::cout << "The value of val_g = " << function_g() << std::endl;
	std::cout << "The value of val_g = " << function_g() << std::endl;
	std::cout << "The value of val_g = " << function_g() << std::endl;
}

Figure 1 Result of code example 1

"function_g" executes the increment of the global variable, and "function_x" does the same work for the local variable. The result clearly indicates that only the value of the global variable increased when "function_g" is executed. This is because every time "function_x" is executed, a new local variable is created and destroyed at the end of the function. Let's see what happens behind the scene using the debugger.

 

1.1) Debugging of the local variable

 

[1] Place breakpoints

: There are five breakpoints placed next to "function_x" and its corresponding function calls.

Figure 2 Debugging of code example 1

[2] Run debugger and find &val_x

: During the first function call, the value of "val_x" is initialized as 0. Since the variable is declared on the stack memory, it has 4byte guard bytes (cc) back and front.

Figure 3-1 Change of the local variable "val_x"

[3] Press F10 and proceed

: The value of "val_x" is changed to 1.

Figure 3-2 Change of the local variable "val_x"

 

[4] Press F10 and proceed

: During the second function call, the value of "val_x" is initialized as 0 again. The second "val_x" is not the same as the first, but the new variable created within the function call.

Figure 3-3 Change of the local variable "val_x"

 

[5] Press F10 and proceed

: The value of "val_x" is changed to 1 again.

Figure 3-4 Change of the local variable "val_x"


1.2) Debugging of the global variable

 

[1] Place breakpoints

: There are five breakpoints placed next to "function_g" and its corresponding function calls.

Figure 4-1 Debugging of the global variable "g_bal"

[2] Run debugger and check the address of the variable

: As you can see down below, the address of the global variable does not change as the local variable: however, the value of the variable is not initialized by each function call. This is because the birth and demise of the global variable are with the lifetime of the program, but with the local scope.

 

Figure 4-2 Debugging of the global variable "g_bal" 
Figure 4-3 Debugging of the global variable "g_bal"
Figure 4-4 Debugging of the global variable "g_bal"

 


2. Static variable

The behavior of a static variable is the same as that of the global variable shown in the preceding example. Static variables are created at the beginning of a program and later destroyed when the program ends. Code example 2 and the corresponding debugging shows how static variables work very well.

 

Code example 2

#include <iostream>
#include <string>
using String = std::string;

int function_x() {
	// Local variable but static
	static int val_x = 0;
	return ++val_x;
}

int main() {
	std::cout << "The value of val_x = " << function_x() << std::endl;
	std::cout << "The value of val_x = " << function_x() << std::endl;
	std::cout << "The value of val_x = " << function_x() << std::endl;
}

Figure 5 Result of code example 2

"static" keyword is added to the local variable "val_x" and the other parts of the program are the same as of the previous example. The result well shows the behavior of the static variable. The value of "val_x" increases by 1 every time the function is called. Now let's see the debugging of the preceding example.

 

[1] Place breakpoints

: Six breakpoints are placed for the function and its corresponding calls.

Figure 6 Debugging of code example 2

[2] Run the debugger and check the address of the static variable

: The address is initialized as 0 and increases by 1 after each function call.

Figure 6-1 Debugging of code example 2
Figure 6-2 Debugging of code example 2
Figure 6-3 Debugging of code example 2
Figure 6-3 Debugging of code example 2

 

The only difference between global variables and local static variables is the local static variables are created when the corresponding scopes are called.

 

2.1) Static functions

Static can also be used for functions but the behavior of static functions and static variables are not alike. When the "static" keyword is brought for a function, the compiler does not look for other cpp files to find the definition of the function, but only the current cpp file. 

 

As we already talked about how the compiler and linker work in C++ previously (2020/05/06 - [Programming/C++] - [Basic C++] 03 - How is C++ code formed : Code Structure, Compiling and Linking), you can define a function in another cpp source file and use the prototype of the function in the current source file in the same project. During the linking process, the C++ linker looks for the corresponding function definition through multiple cpp source files automatically.

 

On the other hand, the compiler does not do the same process for static functions. In other words, the definition of a static function must exist in the current cpp source file. The following example explains it well.

 

Code example 3

// Source_1.cpp
#include <iostream>
#include <string>
void s_function() {
	std::cout << "Source_1.cpp : This is a function.\n";
}
void e_function();

int main() {
	s_function();
	e_function();
}

// Source_2.cpp
#include <iostream>
void e_function() {
	std::cout << "Source_2.cpp : This is an external function.\n";
}

Result

This is a function.
This is an external function.

 

Two functions are defined within the two different cop files; source1 is the main entry file with "s_function" and source2 is an additional file with "e_function". Even though "e_function" is defined in another source file, the C++ compiler raises no errors. The prototype of a function suffices to tell the compiler that "e_function" is defined somewhere in the same project and the linker will find the corresponding definition later.

 

Now, let's add the "static" keyword in front of the function. As you may assume, this code will not be compiled but a C2129 error occurs, as the function has not been defined within the same source file. It means that static functions only exist in the same source file with its function call.

static void e_function();
error C2129: static function 'void e_function(void)' declared but not defined

3. Const variable

Const variables are literally constant and unchanging. Creating const variables only needs the "const" keyword in front of any target variables. I often use "const" to create mathematical variables like Pi (3.14159). Once const variables are created, they can not be altered during the runtime.

 

If we try to do the same thing as shown in code examples but using a const variable, the source file can not be compiled but an error C2105 arises. We are not gonna talk about the meaning of "l-value" right now. The error message simply means that you can not assign the new incremented value to "val_x".

const int val_x = 0;
 error C2105: '++' needs l-value

The "const" keyword is very convenient, as it prevents a variable from being altered during runtime. The behavior of the "const" is deep and complicated, thus one who uses it frequently must understand how it works in detail.