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';
hello2[1] = 'a';
char *s = const_cast<char *>(hello1);
s[3] = 'x';
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()
{
char t1[] = {'H','e','l','l','o','\0'};
char t2[] = "Hello";
char t3[] = "Hello";
char *s1 = "Hello";
char *s2 = "Hello";
void *v1 = t1, *v2 = t2, *v3 = t3, *v4 = s1, *v5 = s2;
std::cout <<v1<<'\t'<<v2<<'\t'<<v3<<'\t'<<v4<<'\t'<<v5<<std::endl;
0xbffff460 0xbffff450 0xbffff440 0x8048844 0x8048844
*t1 = 'x'; *t2 = 'q'; *ct = 'y';
*s1 = 'w'; *s2 = 'z';
return 0;
}
Optimization questions
#include <iostream>
int f(const int i)
{
return i;
}
int main()
{
const int c1 = 1;
const int c2 = 2;
const int c3 = f(3);
const int *p = &c2;
int t1[c1];
int t2[c2];
int i; std::cin >> i;
switch(i)
{
case c1: std::cout << "c1"; break;
case c2: 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;
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;
....
}
int *f()
{
int i = 2;
....
return &i;
}
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];
...
delete [] p;
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;
delete p2;
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);
...
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);
static int i;
int main()
{
...
}
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;
static std::vector<char> v('x',5);
int main()
{
...
}
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;
}
}
}
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 x2[n] = { 1, 2, 3, 4};
Y y1[n];
Y y2[k];
Y *yp = new Y[k];
std::vector<X> xv(10, X(1));
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;
};
int main()
{
X xx;
xx.i
}
X::X() : i(0) { }
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;
if ( strlen(cs = (s2+s3).c_str()) < 8 && cs[0] == 'a' )
cout << cs;
}
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;
}
Scope
=====
#include <iostream>
int i;
static int j;
extern int n;
namespace X
{
int i;
int f()
{
i = n;
return j;
}
void g();
}
namespace
{
int k;
}
void f()
{
int i;
static int k;
{
int j = i;
int i = j;
std::cout << i << j << k << X::i << ::k << ::i;
}
}
static void g()
{
++k;
}
void X::g()
{
++i;
}
Minimizing the visibility -> minimizing the scope is always a good idea!
int i;
int main()
{
if ( ... )
{
int j = 1;
++i;
}
int j = 0;
while ( j < 10 )
{
i++;
}
for (i = 0; i < 10; ++i)
{
}
++i;
}
int main()
{
for ( int i = 0; i < 10; ++i )
{
}
if ( int i = fork() )
{
}
else
{
}
}
How (not to) make scope-life errors?:
=====================================
#include <iostream>
using namespace std;
char *answer( const char *question);
int main()
{
cout << answer( "How are you? ") << endl;
return 0;
}
char *answer( const char *question)
{
cout << question;
char buffer[80];
cin >> buffer;
return buffer;
}
#include <iostream>
using namespace std;
char *answer( const char *question);
char buffer[80];
int main()
{
cout << answer( "How are you? ") << endl;
return 0;
}
char *answer( const char *question)
{
cout << question;
cin.getline(buffer,80);
return buffer;
}
#include <iostream>
using namespace std;
char *answer( const char *question);
int main()
{
cout << answer( "How are you? ") << endl;
return 0;
}
char *answer( const char *question)
{
cout << question;
static char buffer[80];
cin.getline(buffer,80);
return buffer;
}
#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
$
#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
$
#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
$
#include <iostream>
#include <string>
using namespace std;
string answer( string question);
int main()
{
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
$