Scope and Life
==============


Scope - the AREA in the program to define the meaning of a name
        (variable, function, class)

Life  - the TIME under run-time when the memory area is valid
        There is 3 category of lifetime in C++:
    - static:  mainly for global variables
    - auto:    mainly for local variables
    - dynamic: user-controlled memory



Storage types in C++
====================


In C++ objects are mapped into the memory based on their storage types.
Different storage types means different life rules.




Literals
========

Values are known at compile-time:


const char *hello1 = "Hello world";
      char *hello2 = "Other hello";


hello1[1] = 'a';    // syntax error
hello2[1] = 'a';    // could cause runtime error


char *s = const_cast<char *>(hello1);   // dangerous

s[3] = 'x';         // could be runtime error !



This kind of data stored outside of the program read-write area.
Therefore write attempt is undefined.

There is difference between a string literal and an initialized character
array.


#include <iostream>

int main()
{
    // declaration of three arrays in the user data area
    // read and write permissions for the elements:
    char t1[] = {'H','e','l','l','o','\0'};
    char t2[] = "Hello";
    char t3[] = "Hello";

    // declaration of two pointers in the user data area
    // read and write permissions for the pointers
    // ...and...
    // allocation of the "Hello" literal (possibly) read-only
    char  *s1 = "Hello";    // s1 points to 'H'
    char  *s2 = "Hello";    // ... and s2 likely points to the same place  

    void  *v1 = t1, *v2 = t2, *v3 = t3, *v4 = s1, *v5 = s2;
    std::cout <<v1<<'\t'<<v2<<'\t'<<v3<<'\t'<<v4<<'\t'<<v5<<std::endl;
    // the result (v1, v2 v3 are different, v4 and v5 could be the same):
    0xbffff460   0xbffff450    0xbffff440   0x8048844   0x8048844

    // assignment to array elements:
    *t1 = 'x'; *t2 = 'q'; *ct = 'y';

    // modifying string literal: could be segmentation error:
    *s1 = 'w'; *s2 = 'z';

    return 0;
}



Optimization questions


#include <iostream>

int f(const int i)
{
 // 5_const.cpp: In function `int f(int)':
 // 5_const.cpp:7: increment of read-only parameter `i'
 // ++i;
    return i;
}
int main()
{
    const int c1 = 1;    // no memory needed
    const int c2 = 2;    // need memory   
    const int c3 = f(3); // need memory
    const int *p = &c2;

    int t1[c1];
    int t2[c2];
 // 5_const.cpp: In function `int main()':
 // 5_const.cpp:19: warning: ISO C++ forbids variable-size array `t'
 // int t3[c3];

    int i;  std::cin >> i;
    switch(i)
    {
    case c1: std::cout << "c1"; break;
    case c2: std::cout << "c1"; break;
 // 5_const.cpp: In function `int main()':
 // 5_const.cpp:30: case label does not reduce to an integer constant
 // case c3: std::cout << "c1"; break;
    }
    return 0;
}



Be care with optimizations:


const int ci = 10;
int *ip = const_cast<int*>(&ci);

++*ip;

cout << ci << " " << *ip << endl;    // could be '10 11'






Automatic Life
==============

Objects local to a block (and not declared static) has automatic life.
Such objects are created in the stack. The stack is safe in a multithreaded
environment. Objects created when the declaration is encountered and
destroyed when controll leaves the declaration block.


void f()
{
    int i = 2;  // life starts here with initialization
    ....
}               // life finished here



//
// This is error!!!
// 
int *f()
{
    int i = 2;  // life starts here with initialization
    ....
    return &i;  // likely a run-time error
}               // life finished here





Dynamic Life
============

Objects with dynamic life is created in the free store. The lifetime starts
with the evaluation of a new expression (not the new operator). The life ends
at the delete expression.


char *p = new char[1024];   // life starts here
...
delete [] p;                // life finished here



There are two kind of allocation: one for single objects and one for arrays.
The delete operation must be corresponding with the type of the new operation.


char *p1 = new char[1024];
char *p2 = new char[1024];


delete [] p1;
delete [] p1;   // could be runtime error: p1 deleted twice
delete    p2;   // could be runtime error: p2 points to array



The free store is often referred as the heap. This is not absolutely correct:
free memory (new) and heap (malloc) is not neccessary the same.


char *p = malloc(1024); // allocates in "heap"
...
free(p);


Do not mix   malloc/new  and  free/delete !






Static Life
===========

Global variables, namespace variables, and static data members have static
life. Static life starts at the beginning of the program, and ends at the
end of the program.


date d(2003,3,13);  // life of "d", "i" starts here
static int i;       // static life is initialized automatically

int main()
{                       // initialization/constr call happens here
    ...
}                       // destr call happens here


The order of creation is well-defined inside a compilation unit,
but not defined order between source-files. This can lead to the
static initialization problem


static bool b = true;                   // safe
static std::vector<char> v('x',5);      // unsafe, use singleton!

int main()
{               // initialization/constr call happens before this
    ...
}               // destr call happens here



Later we will learn how "smart pointers" make dynamic memory safe!





Local Static Variables
======================

Local statics are declared inside a function as local variables, but with the
static keyword. The life starts (and the initialization happens) when the
declaration first time encountered and ends when the program is finishing.


int main()
{
    while (... )
    {
        if ( ... )
        {
            static int j = 6;   // initialization happens here once!
        }
    }
}  // destr call happens here (if)





Array Elements
==============

Array elemenst are created with the array itself in order of indeces.
Array elements destroyed when the array itself is deleted.

Built-in arrays by the standard have size known by the compiler, aka constant
expression. Some compiler, like GNU G++ is submissive: accept variable size
arrays with warnings. In C9X variable size arrays are accepted.


#include <vector>

struct X { X(int i) { x = i; }; int x; };
struct Y {                      int y; };

int main()
{
    const int n = 4;
          int k = 4;

 // X x1[n];    // no default constructor
    X x2[n] = { 1, 2, 3, 4};
    Y y1[n];
    Y y2[k];    // k is non-const: error by standard, g++ accepts

 // X *xp = new X[k];   // no default constructor
    Y *yp = new Y[k];

 // std::vector<X> xv(10);        // no default constructor
    std::vector<X> xv(10, X(1));  // ok, copy constructor is used
    std::vector<Y> yv(10);

    delete [] yp;
}





Life of Object Attributes
=========================

(Non-static) data members are created when their holder object is created.
If they have constructor, then their constructor will be woven into the
container object constructor. The subobjects will be initialized by their
constructor. However built-in types have no constructor, so they must be
explicitelly initialized.


struct X
{
    int i;      // will not be initialized
};

int main()
{
  X xx;
  xx.i      // undefined
}

X::X() : i(0)  { }   // use initialization list!



Union Member

A member of a union has two constraints:

Member must not have constructor or destructor
The union must not have static field.




Temporary
=========

Created under the evaluation of an expression and destroyed when
the full expression has been evaluated.


void f( string &s1, string &s2, string &s3)
{
    const char *cs = (s1+s2).c_str();
    cout << cs;     // Bad!!

    if ( strlen(cs = (s2+s3).c_str()) < 8 && cs[0] == 'a' ) // Ok
        cout << cs;     // Bad!!
}


The correct way:

void f( string &s1, string &s2, string &s3)
{
    cout << s1 + s2;
    string s = s2 + s3;

    if ( s.length() < 8 && s[0] == 'a' )
        cout << s;
}


When we assign a name to a temporary, the scope of the name will define
the life of the temporary:


void f( string &s1, string &s2, string &s3)
{
    cout << s1 + s2;
    const string &s = s2 + s3;

    if ( s.length() < 8 && s[0] == 'a' )
        cout << s;  // Ok
} // s1+s2 destroyes here: when the const ref goes out of scope





Scope
=====



// file1.cpp
#include <iostream>

int i;          // global, with external linkage
static int j;   // global, without external linkage
extern int n;   // global, defined somewhere else

namespace X     // namespace X
{
    int i;      // X::i

    int f() // X::f()
    {
        i = n;      // X::i = ::n; 
        return j;   // ::j
    }
    void g(); // X::g()
}

namespace       // anonymous namespace
{
    int k;  // ::k
}

void f()
{
    int i;          // local i
    static int k;   // local k  
    {
        int j = i;  // local j, (i is the one declared above)
        int i = j;  // local i

        std::cout << i << j << k << X::i << ::k << ::i;
    }
}

static void g()
{
    ++k;    // ::k
}

void X::g() // X::g()
{
    ++i;    // X::i
}




Minimizing the visibility -> minimizing the scope is always a good idea!



int i;  // dangerous

int main()
{

    if ( ... )
    {
        int j = 1;
        //...
        ++i;    // ++j instead of j
    }

    int j = 0;
    while ( j < 10 )
    {

        //...
        i++; // instead of j++
    }

    for (i = 0; i < 10; ++i)
    {
        //...
    }

    ++i;    // global i   
}


// good:

int main()
{

    for ( int i = 0; i < 10; ++i )
    {
        // i is local here
    }


    if ( int i = fork() )
    {
        // i != 0:  parent process
    }
    else
    {
        // i == 0:  child process
    }

}




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



//
//  Task: 
//  1. write a question to stdout
//  2. read a string as answer from stdin
//  3. print the answer to stdout
//
//
//  This is a VERY BAD program
//

#include <iostream>

using namespace std;

char *answer( const char *question);

int main()
{
    cout << answer( "How are you? ") << endl;
    return 0;
}

// very bad code!!
char *answer( const char *question)
{
    cout << question;
    char buffer[80];    // local scope, automatic life
                        // char[] converts to char*
    cin >> buffer;      // ERROR1: possible buffer overrun!!
    return buffer;      // ERROR2: return pointer to local: never do this!
}





//
//  make buffer global -> static life
//  works, but visible to many places
//

#include <iostream>

using namespace std;

char *answer( const char *question);
char buffer[80];        // global scope, static life

int main()
{
    cout << answer( "How are you? ") << endl;
    return 0;
}

char *answer( const char *question)
{
    cout << question;
//  char buffer[80];
    cin.getline(buffer,80);  // reads max 79 char + places '\0'
    return buffer;
}




//
//  we use static life, but glocal scope is unnecessary
// 

#include <iostream>

using namespace std;

char *answer( const char *question);
// char buffer[80];     // global scope, static life

int main()
{
    cout << answer( "How are you? ") << endl;
    return 0;
}

char *answer( const char *question)
{
    cout << question;
    static char buffer[80];  // local scope, static life
    cin.getline(buffer,80);
    return buffer;
}






//
//  for advanced programmers :) 
//  the previous is not always fine
//

#include <iostream>

using namespace std;

char *answer( const char *question);

int main()
{
    cout << answer("Sure?: ") << ", " << answer( "How are you?: ") << endl;
    return 0;
}

char *answer( const char *question)
{
    cout << question;
    static char buffer[80];
    cin.getline(buffer,80);
    return buffer;
}








$ g++ -ansi -pedantic -Wall scope4.cpp
$ ./a.out
How are you?: fine
Sure?: yes
yes, yes
$






//
//  Who owns the buffer? -> who wants to use it!
//

#include <iostream>

using namespace std;

char *answer( const char *question, char *buffer, int size);

int main()
{
    const int bufsize = 80;
    char buffer1[bufsize],
    char buffer2[bufsize];

    cout << answer("How are you?: ", buffer1, bufsize) << endl;
    cout << answer("Sure?: ", buffer2, bufsize) << endl;
    return 0;
}

char *answer( const char *question, char *buffer, int size)
{
    cout << question;
    cin.getline(buffer,size);
    return buffer;
}





$ g++ -ansi -pedantic -Wall scope5.cpp
$ ./a.out
How are you?: fine
fine
Sure?: yes
yes
$






//
//  The C++ solution: suing std::string
//

#include <iostream>
#include <string>

using namespace std;

string answer( string question);

int main()
{
    cout << answer("How are you?: ") << endl;
    cout << answer("Sure?: ") << endl;
    return 0;
}

string answer( string question)
{
    cout << question;
    string s;
    getline(cin,s);
    return s;
}






$ g++ -ansi -pedantic -Wall scope6.cpp
$ ./a.out
How are you?: fine
fine
Sure?: yes
yes
$





//
//  But not be "tooooo" clever!!
//

#include <iostream>
#include <string>

using namespace std;

string answer( string question);

int main()
{
    // no sequence point: evaluation order is not defined in expression
    cout << answer( "How are you?: ") << ", " << answer("Sure?: ") << endl;
    return 0;
}

string answer( string question)
{
    cout << question;
    string s;
    getline(cin,s);
    return s;
}



$ g++ -ansi -pedantic -Wall scope7.cpp
$ ./a.out
Sure? yes
How are you? not sure
not sure, yes
$