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