본문 바로가기

Programming/C++ Basic

[Basic C++] 22 - Declaration and Definition

Declaration and definition are the two most fundamental concepts to form programs in C++. Although many programmers including me write code without a full understanding of these concepts and it may lead our code to critical failures or significantly decreased performance.

 

1. Declaration

Declaring a variable is to tell the compiler the existence of the variable at the point of the declaration. By doing so, the compiler recognizes the data type, memory size, and name (indentifier) of the variable. On the other hand, the compiler does not know how much memory the variable occupies in the memory, simply because it has not been defined in detail. For instance, the below code declares an integer "a" in C++.

 

#include <iostream>
int a;

int main () {
 a = 1;
 std::cout << a ;
}

 

The variable "a" is an integer with no initialization and declared in the global scope. Its value is defined as 1 in the main function, and its value is shown at the console. In this case, 4 bytes of memory is allocated for the variable and the value "1" is stored at the memory.

 

The memory allocation process usually occurs in the definition process, not in the declaration process. As "int" is one of the built-in types in C++, the compiler exactly knows how much memory to allocate. Therefore, the below example raises an error.

 

[CODE]

#include <iostream>
class X; // forward declaration

int main() {
	std::cout << sizeof(X);
}

[RESULT]
C2027: use of undefined type 'X'

 

Class "X" is forward declared and the memory of X is calculated using the built-in "sizeof()" function. The "C2027" error saying "use of undefined type 'X'" arises, as the type is not defined.

 

" Forward Declaring " in C++ gives the compiler the notion of the existence of the variable, and says " I am going to define it later, no worries. ". This error can be corrected by defining the 'X'.

 

[CODE]

#include <iostream>
class X {
	int a;
	double b;
};

int main() {
	std::cout << sizeof(X);
}

[RESULT]

16

 

The preceding example shows the fully defined class "X" and its memory size is calculated correctly. But one must be like "WHAT ?", because the size of class "X" having an integer (4 bytes) and a double (8 bytes) variables is "16 BYTES", not "12 bytes". Let's debug it by creating an arbitrary object of X.

 

Figure 1 Breaking points of debugging
Figure 2 Memory address of the object x_obj

The address of "x_obj" is found from &x_obj on the memory window. The result displays that the object occupies 16 bytes of memory. It doesn't return 12 bytes. The compiler, MSVC 2019, executes automatic memory alignment to help the program to efficiently access the data, and quote "to take maximum advantage of a specific hardware architecture". I am going to cover this feature later in a different post.

 

To sum up, a declaration is to tell the compiler the fundamental information of a variable but no more than that. It just informs the compiler that the variable exists somewhere in this source file or a different source file.


2. Definition

By defining a variable, the compiler allocates a certain amount of memory, static or dynamic, for a given data type. Depending on the scope and allocation keyword of the variable, the compiler decides where to allocate the memory; static, heap, and global. The definition gives specific information on declared data to the compiler.

 

#include <iostream>

int g_var = 8;

int main() {
	int s_var = 10;
	int *d_var = new int(5);
}

 

The preceding example shows three variables declared and defined in three different memory areas in C++. The address of each variable is displayed by debugging in the next three figures. 

 

Figure 1 Global variable
Figure 2 Static variable
Figure 3 Dynamic variable

Again, the integer data type is one of the built-in data types in C++, thus the compiler knows how much memory it needs to allocate for the data type. Variables must be fully defined before they are called during runtime, otherwise, an error arises.

 


3. More examples

Data in C++ do not have to be declared and defined within the same source file. In fact, functions and classes are often defined in different source files and header files for the efficiency of a program.

 

■ function - forward declaration

 

Forward declaration is one of the most fundamental techniques in C++ to make use of functions to be defined later. Thus code like the below example works fine.

[CODE]

#include <iostream>

void func_x();
void func_y() {
	std::cout << "Call func_y" <<std::endl;
	func_x();
}
void func_x() {
	std::cout << "Call func_x" << std::endl;
}

int main() {
	func_y();
}

[REUSLT]

Call func_y
Call func_x

"func_x" is declared but not defined, yet "func_y" can call it with no error. After the definition of "func_y", "func_x" is defined. This is a simple example of forward declaration. However, trying to call the value of "func_x" before its full definition will arise an error.

 

■ function - defined in a different source file

 

Functions can also be defined in different source files. During the compilation, the compiler is informed that the forward declaration of a given function is defined somewhere else, and later the linker links the declaration and definition.

 

[CODE]

// Source1.cpp
int func_x(int a, int b) {
	return a + b;
}

// main.cpp
#include <iostream>

int func_x(int, int);

int main() {
	int a = 10;
	int b = 1;
	std::cout << func_x(a, b);
}

[RESULT]

11

Only the prototype of "func_x" is declared in the main.cpp file and the definition of it is in a different source file. When it is compiled, the compiler assumes that the definition of the function is somewhere else in the project and the linker connects the function call to the definition in the sorcue1.cpp file.

 

The "extern" keyword can be used too to explicitly tell the compiler that the function definition is not in the current source file. Functions declared in the global scope are extern by default.

extern int func_x(int, int);

 

■ variable - defined in a different source file

 

A variable also can be declared and defined in two different source files but the "extern" keyword must be brought to explicitly inform the compiler that the variable is defined somewhere else.

 

[CODE]

// Source1.cpp
int var = 100;


// main.cpp
#include <iostream>

extern int var;

int main() {
	std::cout << var;
}


[RESULT]

100

The preceding example shows a global variable "var" is declared as an extern variable and the definition of it is in a different source file. Creating global variables is not often recommended in many programming languages, as ironically it is global.

 

There is ODR (One Definition Rule) in C++ enforcing that only one definition must exist for one variable. More than two definitions for one variable arises an error. As declaration only inform the compiler that the existence of a variable, a variable can be declared many times within the same scope. On the other hand, definition makes the compiler to allocate actual memory for the given variable.

 

Therefore, programmers must make sure that all variables declared and defined within the same scope or translation unit have unique names. When it comes to global variables, they exist across the entire project, thus they are prone to the definition error.

 

■ class - defined in a header file

 

A class can be also defined in a different file, but it must be a header file. The extern keyword does not apply to a class unless the object is a class template.

 

[CODE]

// example.h
#pragma once
#include <iostream>
class X {
private:
	int a = 0;
public:
	X () { std::cout << "Class X is instanciated : this class is in example.h file" << std::endl; }
	int b = 0;
	void print_members() {
		std::cout << a << " " << b << std::endl;
	}
};

// main.cpp
#include <iostream>
#include "example.h"

int main() {
	std::cout << "This is main.cpp file." << std::endl;
	X x_obj;
	x_obj.print_members();
}
[RESULT]

This is main.cpp file.
Class X is instanciated : this class is in example.h file
0 0

 

The preceding example is a program consisting of two separate files; main.cpp file and example.h file. The header file contains class X and it is inserted to the source file using #include. I have not explained what a header file is yet, but consider it as a pre-defined code so that you can recycle it conveniently. <iostream> is also a header file including many convenient functions such as cout and cin.

 

During the compilation process, the entire code in the header files is copied to source files in which they are included. One might say <iostream> is included twice in the preceding example; main.cpp and example.h, and it might cause an error. But no worries. Our compiler is smart enough to understand this and automatically exclude multiple inclusion of the same header file or even variables.