Modern C++ - Common errors regarding scope and life

Financed from the financial support ELTE won from the Higher Education Restructuring Fund of the Hungarian Government.

9. Common errors regarding scope and life

Life and scope rules

In imperative programming languages variables have two important properties:

  1. Life - the TIME under run-time when the memory area is valid and usable.
  2. Scope - the AREA in the program where a name is binded to a memory area.

There are plenty of problems junior C++ programmers meet when make mistakes in scope or life rules.

How (not to) make scope-life errors?:

The task:

  1. write a question to stdout
  2. read a string as answer from stdin
  3. print the answer to stdout
 1 //
 2 //  This is a VERY BAD program
 3 //
 4 #include <iostream>
 5 
 6 using namespace std;
 7 char *answer( const char *question);
 8 
 9 int main()
10 {
11     cout << answer( "How are you? ") << endl;
12     return 0;
13 }
14 char *answer( const char *question)
15 {
16     cout << question;
17     char buffer[80];    // local scope, automatic life
18                         // char[] converts to char*
19     cin >> buffer;      // ERROR1: possible buffer overrun!!
20     return buffer;      // ERROR2: return pointer to local: never do this!
21 }

There are two big errors in the code above:

  1. The cin » buffer call reads charakters into buffer until the first separator. The buffer could be (and sooner or later will be) overflow, i.e. we read more charactres then the room we have. This buffer overflow problem is perhaps the most critical security errors in C++.
  2. The function returns a pointer to an automatic life local variable. When we try to use that pointer, the memory behind it already gone. As the life of the local buffer is over, we may overwrite other values.

Lets try to fix the program with making buffer to global, therefore its life to static. Also we avoid buffer overrun problem using getline.

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 char *answer( const char *question);
 6 char buffer[80];   // global scope, static life
 7 
 8 int main()
 9 {
10     cout << answer( "How are you? ") << endl;
11     return 0;
12 }
13 char *answer( const char *question)
14 {
15     cout << question;
16 //  char buffer[80];
17     cin.getline(buffer,80);  // reads max 79 char + places '\0'
18     return buffer;
19 }

This is working (in this example), but buffer is visible in too many places. This is a maintenance nightmare. In fact, buffer is not a global concept in this program, it is only an implementation detail of answer function.

We should try to narrow the scope of buffer.

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 char *answer( const char *question);
 6 // char buffer[80];   // global scope, static life
 7 
 8 int main()
 9 {
10     cout << answer( "How are you? ") << endl;
11     return 0;
12 }
13 char *answer( const char *question)
14 {
15     cout << question;
16     static char buffer[80];  // local scope, static life
17     cin.getline(buffer,80);
18     return buffer;
19 }

This works as we expected, and the scope of buffer is minimal. The buffer is not visible but still valid outside of the answer function.

However, this solution is also far from perfect:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 char *answer( const char *question);
 6 
 7 int main()
 8 {
 9     cout << answer("Sure?: ") << ", " << answer( "How are you?: ") << endl;
10     return 0;
11 }
12 char *answer( const char *question)
13 {
14     cout << question;
15     static char buffer[80];
16     cin.getline(buffer,80);
17     return buffer;
18 }
$ g++ -ansi -pedantic -Wall -W howareyou.cpp
$ ./a.out 
How are you?: fine
Sure?: yes
yes, yes

(Also, consider the reverse evaluation order for the two calls.)

The problem is that we have only one buffer for the two answers, and the second answer overwrites the first one. The second answer will be printed twice.

In real world the same situation happens with concurrent programs executing multiply threads.

We need a separate buffer for each simultanious calls of answer. Lets try this with dynamic memory.

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 char *answer( const char *question);
 6 
 7 int main()
 8 {
 9     cout << answer("Sure?: ") << ", " << answer( "How are you?: ") << endl;
10     return 0;
11 }
12 char *answer( const char *question)
13 {
14     cout << question;
15     char *buffer = new char[80];
16     cin.getline(buffer,80);
17     return buffer;
18 }
$ ./a.out 
How are you?: fine
Sure?: yes
yes, fine

We finally have got two separate answers (in wrong order). But the real problem is hidden: no one freed the allocated buffers. In long run-time, with many calls of answer() we will run out of the memory!

This fenomenon is called memory leak and it is a fatal error in C++.

The right solution is to

  1. Having an exact owner of every memory area. This case the owner is the caller function (here the main()).
  2. Use sequence points to separate sequential events.
 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 char *answer( const char *question, char *buffer, int size);
 6 
 7 int main()
 8 {
 9     const int bufsize = 80;
10     char buffer1[bufsize],
11     char buffer2[bufsize];
12 
13     cout << answer("How are you?: ", buffer1, bufsize) << endl;
14     cout << answer("Sure?: ", buffer2, bufsize) << endl;
15     return 0;
16 }
17 char *answer( const char *question, char *buffer, int size)
18 {
19     cout << question;
20     cin.getline(buffer,size);
21     return buffer;
22 }

This is correct, but also hard to maintain solution. The main function, which uses the memory allocates it. The answer function receives the parameters, and fills the buffer. The maximum size of the characters to read is passed as an extra parameter.

However, in C++ we can use the standard library sdt::string class. There are a lot of advantages of using std::string.

 1 #include <iostream>
 2 #include <string>     // for std::string class
 3 
 4 using namespace std;
 5 
 6 string answer( string question);
 7 
 8 int main()
 9 {
10     string a1 = answer("How are you? ");
11     string a2 = answer("Sure? ");
12     cout << a1 << ", " << a2 << endl;
13     return 0;
14 }
15 string answer( string question)
16 {
17     cout << question;
18     string answ;
19     getline( cin, answ);
20     return answ;
21 }

This code not only works well (even in multithreaded code) but also looks more natural.

$ ./a.out 
How are you? fine
Sure? yes
fine, yes

Use the most straitforward solutions with the help of the standard library classes!