Table of Contents
Memory-models in C++. New and delete operators. Constructors, exceptions and memory-leaks. The auto_ptr - and when it does not help. Write your own smart pointer. Arrays are not polymorphic! C++ object internals.
In C++ objects are mapped into the memory based on their storage types. Different storage types means different life rules.
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; }
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
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 correct: free memory (new) and heap (malloc) is not neccessary the same.
char *p = malloc(1024); // allocates in "heap" ... free(p);
Global variables, namespace variables, and static datamembers 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 no defined order between source-files.
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
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 // X *xp = new X[k]; // no default constructor Y *yp = new Y[k]; // std::vector<X> xv(10); // no default constructor std::vector<Y> yv(10); delete [] yp; }
(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.
A member of a union has two constraints:
Member must not have constructor or destructor
The union must not have static field.
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