Storage types in C++

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



Constant Data


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


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;
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



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
        }
}                               // 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