• Nem Talált Eredményt

Storage classes of variables

In document Mechatronic Systems Programming in C++ (Pldal 148-155)

II. fejezet - Modular programming in C++

3. Namespaces and storage classes

3.1. Storage classes of variables

The results:

Function<int,char>(1, 'c') Function<double,double>(1.5, 0) Function<int,double>(0, 0) Function<int,char>(0, 0) Function<char,char>('A', 'X') Function<int,int>(12, 23)

3. Namespaces and storage classes

In C++, it is easy to create an unstructured source code. In order that a bigger program code remains structured, we have to respect many rules that the syntax of C++ does not require; however, these rules have to be observed for usability purposes. Most of these rules to be applied are the direct consequence of modular programming.

In order to keep code visibility efficiently, we should divide a program in many parts which have independent functionality and which are therefore realized (implemented) in different source files or modules (.cpp). The relation between modules is ensured by interface (declaration) files (.h). In each module, the principle of data hiding should always be respected: this means that from the variables and functions defined in each module, we only share with the others those that are really necessary while hiding other definitions from the outside.

The aim of the second part of the present book is to present the C++ tools that aim at implementing well-structured modular programming. The most important element of that is constituted by the already presented functions; however, but other solutions are also needed:

• Storage classes determine explicitly for every C++ variable its lifetime, accessibility and the place of memory where it is stored. They also determine the visibility of functions from other functions.

• Namespaces ensure that the identifiers used in programs consisting of more modules be used securely.

3.1. Storage classes of variables

When a variable is created in a C++ code, its storage class can also be defined besides its type. A storage class

• defines the lifetime or storage duration of a variable, that is the time when it is created and deleted from memory,

• determines the place from where the name of a variable can be accessed directly – visibility, scope – and also determines which name designates which variable – linkage.

As it is already known, variables can be used after they have been declared in their respective compilation unit (module). (We should not forget that a definition is also a declaration!) We mean by compilation units all C++

source files (.cpp) together with their integrated (included) header files.

A storage class (auto, register, static, extern) can be assigned to variables when they are defined. If it lacks from the definition, compilers assign them automatically a storage class on the basis of the place where the definition is placed.

The following example that converts an integer number into a hexadecimal string demonstrates the usage of storage classes:

#include <iostream>

using namespace std;

static int bufferpointer; // module level definition

// n and first are block level definitions

void IntToHex(register unsigned n, auto bool first = true) { extern char buffer[]; // program level declaration auto int digit; // block level definition

// hex is block level definition

static const char hex[] = "0123456789ABCDEF";

extern char buffer[]; // program level declaration

extern char buffer [123] = {0}; // program level definition

In blocks, the default storage class is auto and, as it can be seen in the example, we stroke that word through because we practically never use that modifier. The storage class of variables defined outside function blocks are external by default; however, the keyword extern only has to be provided when we declare (not define!) external variables.

3.1.1. Accessibility (scope) and linkage of variables

The notion of storage class is strongly related to that of variable accessibility and scope. Every identifier is visible only within its scope. There are three basic scope types: block level (local), file level and program level (global) scope (II.7. ábra - Variable scopes).

The accessibility of variables is determined by default by the place where they are defined in the source code.

We can only create block level and program level variables in that way. However, we can also assign variables a storage class directly.

The notion of scope is similar to that of linkage but it is not completely equal to that. The same identifiers can be used to designate different variables in different scopes. However, the identifiers declared in different scopes or the identifiers declared more than once in the same scope can reference the same variable because of linkage. In C++, there are three types of linkage: internal, external and no linkage.

In a C++ source code, variables can have one of the following scopes:

block level A variable of this type is only visible in the block accessible any more. If a variable is defined on a block level without the storage classes extern and static, it does not have any linkage.

file level A file level variable is only visible in the module containing its declaration. These variables can only be referenced from the functions of the module but not

from other modules. Identifiers having file level scope are those that are declared outside the functions of the module and that are declared with internal linkage using the static storage class. (It should be noted that the C++ standard declared deprecated the usage of the keyword static for that purpose, and it recommends using anonymous namespaces for selecting internal linkage variables).

program level A program level variable is accessible from the functions of all the modules (all compilation units) of a program. Global variables that are declared outside the functions of a module (that is with external linkage) have program level scope. For that purpose, global variables should be declared without a storage class or with the extern storage class.

File and program level scopes can be divided furthermore by namespaces presented later which introduce the notion of qualified identifiers.

II.7. ábra - Variable scopes

3.1.2. Lifetime of variables

A lifetime is a period of program execution where the given variable exists. On the basis of lifetime, identifiers can be divided into three groups: names of static, automatic and dynamic lifetime.

static lifetime Identifiers having a static or extern storage class have a static lifetime. The memory space allocated for static variables (and the data contained within it) remains until the end of program execution. A variable of this type is initialised once: when the program is launched.

automatic lifetime Within blocks, variables defined without the static

storage class and the parameters of functions have automatic (local) lifetime. The memory space of automatic lifetime variables (and the data contained within it) exist only in the block where they are defined. Variables with local lifetime are created on a new segment of memory each time their block is entered, and they are deleted when the block is exited (their content becomes lost). If a variable of this type is assigned an initial value, this initialization takes place every time the variable is created.

dynamic lifetime Independently of their storage classes, memory blocks that are allocated by the operator new and deallocated by the operator delete have dynamic lifetime.

3.1.3. Storage classes of block level variables

Variables that are defined within a function/program block or in the parameter list of functions are automatic (auto) by default. The keyword auto is not used in general. In addition, in the latest C++11 standard, this keyword has a new interpretation. Block level variables are often called local variables.

3.1.3.1. Automatic variables

Automatic variables are created when control is passed to their block and they are deleted when that block is exited.

As we have already said in previous chapters, compound statements (blocks) can be nested into each other. Each block can contain definitions (declarations) and statements in any order:

{

definitions, declarations and statements

}

An automatic variable becomes temporarily inaccessible if another variable with the same name is defined in an inner block. So it is hidden until the end of that inner block and there is not any possibility to access the hidden (existing) variable:

int main() {

double limit = 123.729;

// the value of the double type variable limit is 123.729 {

long limit = 41002L;

// the value of the long type variable limit is 41002L }

// the value of the double type variable limit is 123.729 }

Since automatic variables cease to exist when the function containing them is exited, it is forbidden to create a function that returns the address or reference to a local variable. If we want to allocate space within a function for a variable that is used outside the function, then we should allocate that memory space dynamically.

In C++, loop variables defined in the header of a for loop are also automatic:

for (int i=12; i<23; i+=2) cout << i << endl;

↯ cout << i << endl; // compilation error: i is not defined

The initialization of automatic variables always takes place if control is passed to their block. However, it is only the variables defined with an initial value that receive an initial value. (The value of the other variables is undefined!).

3.1.3.2. The register storage class

The register storage class can only be used for automatic local variables and function parameters. If we provide the keyword register for a variable, we ask the compiler to create that variable in the register of the processor, if possible. In that way, the compiled code may be executed faster. If there is no free register for that purpose, the variable is created as an automatic variable. However, register variables are created in memory if the address operator (&) is used with them.

It should be noted that C++ compilers are able to optimize the compiled code, which results in general in a more efficient program code than if the keyword register was used. So it is not surely more efficient to use register storage classes.

The following Swap() function can be executed much faster than its previously presented version. (We use the word "can" because it is not known in advance how much register storage class specification takes into consideration the compiler.)

void Swap(register int &a, register int &b) { register int c = a;

a = b;

b = c;

}

The function StrToLong() of the following example converts a decimal number stored in a string into a value of type long:

long StrToLong(const string& str) {

register int index = 0;

register long sign = 1, num = 0;

// leaving out leading blanks for(index=0; index < str.size()

&& isspace(str[index]); ++index);

// sign?

if( index < str.size()) {

if( str[index] == '+' ) { sign = 1; ++index; } if( str[index] == '-' ) { sign = -1; ++index; } }

// digits

for(; index < str.size() && isdigit(str[index]); ++index) num = num * 10 + (str[index] - '0');

return sign * num;

}

The lifetime, visibility of register variables and the method of their initialization equal with those of variables having an automatic storage class.

3.1.3.3. Local variables with static lifetime

By using static storage classes instead of auto, we can create block level variables with static lifetime. The visibility of a variable like this is restricted to the block containing its definition; however, the variable exists from the time the program is launched until the program is exited. Since the initialization of static local variables takes place only once (when they are created), these variables keep their value even after the block is exited (between function calls).

The function Fibo() of the following example determines the next element of a Fibonacci sequence from the 3th element. Within the function, the static local variables a0 and a1 keep the value of the last two elements even after the block is exited.

#include <iostream>

using namespace std;

3.1.4. Storage classes of file level variables

Variables defined outside functions automatically have extern, i.e. program level scope. When software is developed from many modules (source files), it is not sufficient to use global variables in order that the principles of modular programming be observed. We might also need variables defined on module level: the access of these variables can be restricted to the source file (data hiding). This can be achieved by assigning extern variables a static storage class. If constants are defined outside functions, they have also internal linkage by default.

In the following program generating a Fibonacci sequence, the variables a0 and a1 were defined on a module level in order that their content remain accessible from another function (FiboInit()).

#include <iostream>

using namespace std;

const unsigned first = 0, second = 1;

static unsigned a0 = first, a1 = second;

provide an initial value for them in their definition, then the compiler initializes these variables automatically to 0 (by filling up its memory space with 0 bytes). In C++, static variables can be initialized by any expression.

C++ standards recommend using anonymous namespaces instead of static external variables (see later).

3.1.5. Storage classes of program level variables

Variables that are defined outside functions and to which a storage class is not assigned have an extern storage class by default. (Of course, the keyword extern can be used here, even if that is the default case.)

The lifetime of extern (global) variables begins with the launching and ends with the end of the program.

However, there might be problems with visibility. A variable defined in a given module can only be accessed

from another module, if the latter contains the declaration of that variable (for example by including its declaration file).

When using program level global variables, we have to pay attention whether we define or declare them. If a variable is used without a storage class specification outside functions, this means defining this variable (independently of the fact that it is provided an explicit initial value or not). On the contrary, assigning them extern explicitly can have two consequences: without initial value, they are declared , with initial value, they are defined . The following examples demonstrate what we have said so far:

Same definitions (only one of them can be used) Declarations

double sum;

extern const int size = 7; extern const int size;

It should be kept in mind that global variables can be declared anywhere in a program but the effects of these declarations start from the place of the declaration until the end of the given scope (block or module). It is a frequent solution to store declarations in header files for each module and to include them at the beginning of every source file.

The initialization of global variables takes place only once when the program starts. In case we do not provide any initial value to them, then the compiler initializes these variables automatically to 0. In C++, global variables can be initialised by any expression.

It should be kept in mind that global variables should only be used with caution and only if they are really needed. Even in the case of a bigger program, only some central extern variables are allowed.

In document Mechatronic Systems Programming in C++ (Pldal 148-155)