Course List: http://www.c-jump.com/bcc/
Memory consists of consecutive, numbered storage cells:
Each memory cells has a unique address:
Consider:
int main() { int x = 1; int y = 2; y = y + x; return 0; }
How many CPU instructions does it take to compute the following statement?
y = y + x;
y = y + x;
A disassembly of a debug build on Windows 32-bit platform shows
mov eax, dword ptr [y] ; load y from memory into CPU register EAX add eax, dword ptr [x] ; compute EAX + x, save result in EAX mov dword ptr [y], eax ; save result from EAX in memory that belongs to y
Only one instruction (add) deals with the math.
The other two instructions (mov) transfer values to/from memory.
Can CPU add two memory values in one step?
Can CPU add two memory values in one step?
No. The CPU can only access one memory location per instruction. This is a limitation of the CPU architecture:
In early days of computing, programmers wrote machine instructions in binary codes. For example,
10001011 11111000
The first byte 10001011 indicates code of instruction (e.g. mov)
The second byte 11111000 indicates the memory location of data
On paper machine codes were often written with square brackets to delineate a memory location:
10001011 [ 11111000 ]
Modern Assembly languages retain square bracket notation:
mov eax, dword ptr [y] ; load y from memory into CPU register EAX
From the Assembly prospective, y is actually the location of variable y in memory:
mov eax, dword ptr [ 11111000b ]
Here, the little b at the end of the 11111000 tells the Assembly that this is a binary number.
Because C is a system programming language, it must provide direct access to physical hardware, including memory.
C supports Assembly language style to access memory. For example,
int MEMORY = 0; // start at the beginning of memory int location = 248; // set location of memory cell #248 MEMORY[ location ] = 65; // save 65 in memory int x = MEMORY[ location ]; // assign value from memory to x
The above C code uses square brackets to access memory cell #248, counting from the beginning of memory.
This code would compile just fine on PDP-7.
Images courtesy of the Computer History Museum, San Jose, CA,
computerhistory.org.
See
http://www.soemtron.org/pdp7no47systeminfo.html
for details.
World's first Unix machine (originally Unics, a sarcastic reference to Multics)
Original computer that Dennis Ritchie and Ken Thompson wrote the first Unix operating system on at Bell Labs in 1967
The PDP-7 was an 18-bit machine.
Was notable for its solid construction - weighing just over half a ton!
The Space Travel game was ported to PDP-7 from Multics.
The Multics project: multiuser, multiprocessor, platform-independent operating system.
Developed as a commercial system by GE, MIT, and Bell Labs.
Project started in 1965. Bell Labs pulled from the project in 1969.
Multics greatly influenced the design of the Unix operating system, especially the hierarchical file system and the command shell.
See web.mit.edu/multics-history/ for details.
Our previous example needs an important modification to compile properly using today's C/C++ compiler:
int MEMORY = 0; // start at the beginning of memory int location = 248; // set location of memory cell #248 MEMORY[ (char*) location ] = 65; // save character A in memory char x = MEMORY[ (char*) location ]; // assign value from memory to character x
The expression
(char*) location
tells the compiler that we are accessing a specific data type -- a char in our example.
Specifying the data type was not a requirement on PDP-7, because PDP-7 supported only one uniform data type, an 18-bit integer:
MEMORY[ location ] = x; // save data in physical memory x = MEMORY[ location ]; // load data from physical memory
Later computer models begin to support data types of different sizes. Since then, the compiler requires an indication of the data type:
MEMORY[ (char*) location ] // access character in memory MEMORY[ (int*) location ] // access integer in memory MEMORY[ (float*) location ] // access float in memory MEMORY[ (double*) location ] // access double in memory MEMORY[ (bool*) location ] // access boolean in memory
Note that using syntax without an asterisk
MEMORY[ (char*)location ] // correct syntax MEMORY[ (char)location ] // incorrect syntax
cannot not be applied, because (char) already has a meaning of the data type cast operator:
int x = 65; char c = (char)x; // (char) explicitly converts x to char
#include <iostream> using namespace std; int main() { // Each time our program executes, it can be loaded into a // different physical memory. Yet we want to apply addresses // from the beginning of memory. The MEMORY variable represents // the beginning of memory at location zero: int MEMORY = 0; // "HELLO" is stored in memory as a sequence of bytes. // To keep track of that sequence of bytes, compiler // uses memory location of the first byte: int location = int( "HELLO" ); cout << "The location is " << location << '\n'; for(;;) { cout << "Enter new location: "; cin >> location; // Show character at the specified location: cout << MEMORY[ (char*)location ] << "\n"; // (char*) means "get a character from the specified location" // We need (char*) because otherwise compiler doesn't know what // type of object to retrieve from memory. } return 0; }
1950 Assembly \ 1958 \ Algol \ / 1967 BCPL-------/ Simula-67 \ / / 1968 \ Algol68 / 1969 B / / \ / / 1971 C / / \ / 1985 / C++ 1989 ANSI C \ 1994 Java \ 2002 C#
BCPL: Basic Combined Programming Language (or "Before C" programming language)
Uses MEMORY!location notation instead of MEMORY[location]
|
|
#include <iostream>
using namespace std;
int main()
{
int MEMORY = 0;
//int location = int( "HELLO" ); // BEFORE
char* location = "HELLO"; // NOW
for(;;) {
// Temporarily commented out
// cout << "Enter new location: ";
// cin >> location;
// Show character at the specified location:
//cout << MEMORY[ (char*)location ] << "\n"; // BEFORE
cout << MEMORY[ location ] << "\n"; // NOW
break;
}
return 0;
}
#include <iostream> using namespace std; int main() { //int MEMORY = 0; // BEFORE char* location = "HELLO"; // NOW for(;;) { int offset; cout << "character at offset: "; cin >> offset; // Show character at the specified location: //cout << MEMORY[ (char*)location ] << "\n"; // BEFORE //cout << MEMORY[ location ] << "\n"; // BEFORE cout << location[ offset ] << "\n"; // NOW break; } return 0; }
#include <iostream> using namespace std; int main() { char* location = "HELLO"; // If offset is zero, we can access memory by cout << location[ 0 ] << "\n"; // The following does the same thing: cout << *location << "\n"; return 0; }
Obtaining something from a memory location at the specified address is called dereference
Expression *location uses dereference operator (or indirection operator)
|
|
In 1971-73 Dennis Ritchie turned B into the C language, keeping most of the syntax but adding data types and other features.
Unix is rewritten in C in 1973 on DEC PDP-11 computer
|
|
#include <iostream> using namespace std; int main() { double price = 1.52; // using address-of operator to obtain address // of the price variable in memory: double * location = &price; // The following two lines do the same thing: // access memory at a given location cout << location[ 0 ] << "\n"; // using [subscript] notation cout << *location << "\n"; // dereference operator * return 0; }
|
|
|
|
There are two ways to pass a pointer to a function:
#include <iostream> using namespace std; // function gets a pointer to double void take_pointer( double * ptr ) { cout << *ptr << '\n'; } // function gets an "array" of doubles void take_fossil( double ptr[] ) { cout << ptr[ 0 ] << '\n'; } int main() { double price = 1.52; double * location = &price; take_pointer( location ); take_fossil( &price ); return 0; }
Pointer is very similar to a normal integer variable:
#include <iostream> using namespace std; // function declarations void process_zipcodes( int * zip_codes, int size ); int main() { //------------------------------------------------------------- // (a) we can find out how far apart our objects are in memory //------------------------------------------------------------- double car_price = 17000.00; double milk_price = 3.76; double * ptr1 = &car_price; double * ptr2 = &milk_price; // Units of measurement - one double - not one byte! int distance = ptr1 - ptr2; cout << "Distance between two double variables in memory is " << distance << '\n'; //-------------------------------------------------------------------- // (b) we can use simple arithmetic operations to move pointer around //-------------------------------------------------------------------- int * zip_codes = new int[ 5 ]; /* | zip_codes[0] zip_codes[1] zip_codes[2] zip_codes[3] zip_codes[4] | ?????_______ ?????_______ ?????_______ ?????_______ ?????_______ */ zip_codes[ 0 ] = 2184; zip_codes[ 1 ] = 90210; zip_codes[ 2 ] = 12345; zip_codes[ 3 ] = 54321; zip_codes[ 4 ] = 77889; /* *zip_codes | zip_codes[0] zip_codes[1] zip_codes[2] zip_codes[3] zip_codes[4] | 02184_______ 90210_______ 12345_______ 54321_______ 77889_______ */ ++zip_codes; cout << *zip_codes << '\n'; /* *zip_codes | zip_codes[-1] zip_codes[0] zip_codes[1] zip_codes[2] zip_codes[3] | 02184_______ 90210_______ 12345_______ 54321_______ 77889_______ */ zip_codes = zip_codes + 2; /* *zip_codes | zip_codes[-3] zip_codes[-2] zip_codes[-1] zip_codes[0] zip_codes[1] | 02184_______ 90210_______ 12345_______ 54321_______ 77889_______ */ cout << *zip_codes << '\n'; cout << zip_codes[ -3 ] << '\n'; //-------------------------------------------------------------------- // (c) we can pass information to a function via pointer //-------------------------------------------------------------------- process_zipcodes( zip_codes, 2 ); /* *zip_codes | zip_codes[-3] zip_codes[-2] zip_codes[-1] zip_codes[0] zip_codes[1] | 02184_______ 90210_______ 12345_______ 77889_______ 77889_______ */ cout << zip_codes[ 0 ] << '\n'; cout << zip_codes[ 1 ] << '\n'; process_zipcodes( zip_codes - 3, 5 ); return 0; } void process_zipcodes( int * zip_codes, int size ) { cout << "\t Inside process_zipcodes()\n"; cout << *zip_codes << '\n'; cout << zip_codes[ -3 ] << '\n'; *zip_codes = zip_codes[ 1 ]; cout << "\t Exiting process_zipcodes()\n"; }
to understand billions of lines of code already written in C/C++
to access individual characters in the string: "HELLO"
to allow indirect function parameters
to avoid copying large objects when passing them to functions
to access memory that was allocated dynamically
to navigate arrays of variables
to build dynamic data structures, such as lists and trees
to use many OOP features of C++, e.g. operator overloading and polymorphism
Memory consists of consecutive, numbered storage cells:
Pointer is a variable that holds a memory address.
Pointer remembers the data type that it points to.
memory[ location ]....access memory at a given address *location.............short-hand for location[0], called "dereference" &variable.............address of a variable or function addr = new int........free store allocation, also new int[] addr[ offset ]........access to free store delete................free store deallocation, also delete[] heap..................C-style dynamic memory management free store............C++-style dynamic memory management array.................block of memory multidim. array.......block of memory