| VARIABLES ADRESSES AND VALUES | 17.01 |
![]() |
The memory of the computer consists of a list of memory locations. Each memory location is characterized by an address, say 00dbe17e. The content of a memory location is called its value, say 01010001. Memory locations are used to store and retrieve values. In a high-level language memory locations are not known as adresses but they are known by name: the name of a constant, a variable, an array or a function. Let us concentrate on variables: The notion of a variable is an abstraction of a memory location or address. When a variable is declared, say, int n; the compiler binds the name n to a free address in memory. The values that the variable can hold are stored at this address n = 81; and this value can be accessed, retrieved and changed using its name n = 2*n; |
API |
17.01 | | 17.02 |
| LHS VALUE AND RHS VALUE | 17.02 |
![]() |
Each variable has a type in order to be able to interpret the value of what is stored at its address. Thus • if n is of type int and its address contains bitpattern 0..001010001 then its value will be 81, • if c is of type char and its address holds 01010001 then its value will be 'Q'. Now what are variables? Variables are essentially entities with two attributes: • address or lhsvalue where does the entity exist? • content or rhsvalue or value what does the entity contain? Assignment is the abstraction of updating the contents of a memory location. Let me make this clear using the update assignment n = 2*n; Here the name of the variable n occurs in two different roles: • on the righthand side (rhs) the name n stands for its value and the expression 2*n evaluates to 162 • on the lefthand side (lhs) the name n stands for its address and n = 162; means that 162 will be stored at this address. |
API |
17.01 | | 17.03 |
| DECLARATIONS ASSIGNMENTS AND COMPILER ACTIONS | 17.03 |
![]() |
The essential difference between address (lhsvalue) and value (rhsvalue) can be made more clear even by putting the following question: Did you ever write 13 = n;? I do not think so: rhsvalues cannot be written on the lhs of an assignment! DECLARATION, ASSIGNMENT, COMPILER ACTIONS and MEMORY MAP: Declaration: int anInt; Assignment: anInt = 13; Compiler actions: • allocate i bytes of memory (00dbe17e .. 00dbe181) with i the number of bytes required to store an integer • add name, type, address, .. to internal symboltable • store value 13 at address of anInt. |
API |
17.02 | | 17.04 |
| DECLARATIONS ASSIGNMENTS AND MEMORY MAPS | 17.04 |
![]() |
This memory map (and many that follow) shows: • the name of the variable • the address chosen by the compiler where does the variable exist? i.e. its lhsvalue or address • the value that has been stored what does the variable contain? i.e. its rhsvalue or simply value. Since real addresses are awkward to write and read (as demonstrated in the previous picture) we will simplify them to 1004-1007, 1008-1011, .... If a variable has not been initialized we will write ? to denote its value. |
API |
17.03 | | 17.05 |
| THE POINTER CONCEPT | 17.05 |
![]() |
Let us take a bold step! • a lhsvalue is an address • declare variables that can hold an address: why not? A variable that can hold an address is called a pointer variable or simply a pointer or reference. A pointer is said to point at or refer to the address it holds for reasons that will soon be clear, I hope. In C a pointer variable is declared by preceding its name by *: int *anIntPtr; declares a pointer variable with the name anIntPtr. Reading right to left: • anIntPtr is a pointer to an integer, or • *anIntPtr is an expression that evaluates to an integer. |
API |
17.04 | | 17.06 |
| DECLARATION OF POINTER | 17.06 |
![]() |
Declaration: int *anIntPtr; Compiler actions: • allocate a bytes of memory (00abc872 .. 00abc875) with a the number of bytes required to store an address • add name, type, address to internal symboltable |
API |
17.05 | | 17.07 |
| ASSIGNMENT OF POINTER | 17.07 |
![]() |
So let us assign the value 00dbe17e of our previous variable anInt: int anInt; int *anIntPtr; anIntPtr = &anInt; |
API |
17.06 | | 17.08 |
| THE ADDRESS OPERATOR & | 17.08 |
![]() |
The assignment of the address of the int variable anInt to the to the pointer variable anIntPtr is written in C as anIntPtr = &anInt; Now the memory map has the following appearance: |
API |
17.07 | | 17.09 |
| THE DEREFERENCING OPERATOR * | 17.09 |
![]() |
An important question now is how to get the value of the variable anInt using anIntPtr which is the pointer that points at it! After declaration and initialization: int n1, n2, *nPtr; int n1 = 13; nPtr = &n1; |
API |
17.08 | | 17.10 |
| SUMMARY | 17.10 |
![]() |
Indeed after n2 = *nPtr; the variable n2 is equal to 13. |
API |
17.09 | | 17.11 |
| PROGRAM 1 | 17.11 |
![]() |
A small program to display a memory map:
#include <stdio.h> // needed for printf()
int main(void) {
int j = 13,
k = 47,
*p = &k; // declare pointer p and initialize it
printf("j is %d and its address is %p\n", j, &j);
printf("k is %d and its address is %p\n", k, &k);
printf("p is %p and its address is %p\n", p, &p);
return(0);
}
|
API |
17.10 | | 17.12 |
| SAMPLE RUN AND MEMORY MAP | 17.12 |
![]() |
SAMPLE RUN: j is 13 and its address is 00bc2610 i.e. &j k is 47 and its address is 00bc260c i.e. &k p is 00bc260c and its address is 00bc2608 i.e. &p |
API |
17.11 | | 17.13 |
| PROGRAM 2 | 17.13 |
![]() |
A small program that summarizes all features discussed sofar.
#include <stdio.h>
int main(void) {
int j = 13,
k = 47,
*p = &k;
printf("%p : address of pointer variable p\n", &p);
printf("%p : the value of p is the address of k\n", p);
printf("%p : the address k (checked)\n", &k);
printf("%d : the value *p is the value of k\n", *p);
printf("%d : the value of k (checked)\n", k);
*p = 87; // change whatever p points at into 87
printf("%d : the new value *p\n", *p);
printf("%d : the value of k (indeed k has changed)\n", k);
printf("%d : the value of k again (i like playing)\n", **&p);
return(0);
}
|
API |
17.12 | | 17.14 |
| SAMPLE RUN AND MEMORY MAP | 17.14 |
![]() |
MEMORY MAP: |
API |
17.13 | | 17.15 |
| POINTER SYNTAX AND PITFALLS | 17.15 |
![]() |
Declarations • int*p; • int *p; • int* p; • int * p; are essentially identical from the compiler's point of view. But int*a, b; declares variable a of type pointer to int and variable b of type int. In order to declare two pointers to int you have to write int *a, *b; |
API |
17.14 | | 17.16 |
| INITIALIZATIONS | 17.16 |
![]() |
Initializations int k, *p; // declare k as int and p as pointer to int p = &k; // initialize p *p = 87; // initialize k can also be written as int k, // declare k as int *p = &k; // init pointer p to int at address of k *p = 87; // initialize k Do not confuse the expressions int *p = &k; which declares pointer p and assigns the address value &k and *p = 87; which assigns value 87 to k (which is pointed at by p). |
API |
17.15 | | 17.17 |
| CONFUSION AND WARNING | 17.17 |
![]() |
This confusion can be avoided easily using the typedef mechanism of C: typedef int* pint; // pint is a new type name for pointer to int int k = 87; // declare int k and initialize on 87 pint p = &k; // declare pint p and initialize on &k *p = k + 3; // now *p is equal to 90 and so is k! WARNING: Consider the following fragment int k = 47, *p; declare and initialize int k and declare pointer p to int *p = 87; ERROR!!! Indeed: • pointer p has been declared but not initialized!! • p points nowhere! (but ...) • and the assignment is an error that will not be detected! ALWAYS INITIALIZE YOUR POINTERS!! |
API |
17.16 | | 17.18 |
| ALWAYS INITIALIZE POINTERS | 17.18 |
![]() |
ALWAYS INITIALIZE YOUR POINTERS!! • One way is by p = NULL; And now the error will be detected since a null-pointer cannot be dereferenced. • The other way has been shown above p = &k; a valid address is assigned to pointer p and now pointer p refers to integer k and k can be changed using *p. Notes: • NULL is the universal pointer constant that can be assigned to any pointer. • printf("%p", NULL); yields 00000000. |
API |
17.17 | | 17.19 |
| CALL BY VALUE AND CALL BY REFERENCE | 17.19 |
![]() |
Functions in C returnType functionName(formalParameterList) { // definition or implementation } returnType • void • any value type • any pointer type formalParameterList • void • comma-separated sequence of formalParameter formalParameter typeIdentifier followed by variableIdentifier NB: function prototype: returnType functionName(formalParameterList); Function prototypes are required in ANSI C! |
API |
17.18 | | 17.20 |
| IMPORT AND EXPORT PARAMETERS | 17.20 |
![]() |
The formal parameters can always be classified: • VALUE PARAMETERS are used to import the necessary data for the function to do its job. The import mechanism is known as CALL BY VALUE and its properties will be summarized below. • REFERENCE PARAMETERS are used to export the results after the function has done its job, can also be used for import: the function modifies the imported data and exports the modified data using the same parameters. The export mechanism is known as CALL BY REFERENCE and this mechanism (in the language C) will be discussed thoroughly. First consider some examples to get the idea of import and export. int min(int n1, int n2) { // determine minimum of n1 and n2 and return result } usage: theMin = min(a, b); // function min imports a and b |
API |
17.19 | | 17.21 |
| IMPORT AND EXPORT EXAMPLES | 17.21 |
![]() |
Alternatively: void min(int n1, int n2, int *min) { // determine min of n1 and n2 and return result by ref } usage: min(a, b, &theMin); Import a, b and &theMin. Export minimum of a and b, i.e. theMin. void minmax(int n1, int n2, int *min, int *max) { // determine min and max of n1 and n2 and return by ref } usage: min(a, b, &theMin, &theMax); Import a, b, &theMin and &theMax. Export minimum and maximum of a and b, i.e. theMin and theMax. void sort3(int *n1, int *n2, int *n3) { // sort n1, n2 and n3 and return results by ref } usage: sort3(&a, &b, &c); Import &a, &b and &c. Export a, b and c such that a <= b <= c. |
API |
17.20 | | 17.22 |
| CALL BY VALUE: PROTOTYPE EXAMPLE | 17.22 |
![]() |
#include<stdio.h> // we use printf() void test(int); // prototype int main(void) { int k = 5; printf("before calling test() k is %d.\n", k); // 5 test(k); printf("after calling test() k is %d.\n", k); // 5 } void test(int n) { printf("inside test() n is %d.\n", n); // 5 printf("now we change n.\n"); n++; printf("now we have changed n.\n"); printf("inside test() n is %d.\n", n); // 6 } |
API |
17.21 | | 17.23 |
| CALL BY VALUE: ESSENTIALS | 17.23 |
![]() |
What happens exactly? At the moment that the function test() is called: • memory is created for formal parameter n • value 5 of actual argument k is copied into formal parameter n. During execution of the function test(): • the value of the local variable n is used and even changed • but whatever happens with n will have no effect on k at all!! After termination of the function test(): • local variables that correspond with the formal parameters do not exist any longer • the value of the variable k that lives outside the function is still 5. |
API |
17.22 | | 17.24 |
| CALL BY VALUE: REMARKS | 17.24 |
![]() |
1 prototype void f(int n); formal parameter n of type int return type void usage f(e); expression e that yields an int Expression e will be evaluated and yields an integer value formal parameter n imports this value. This type of function is not very useful since it does not return anything but writing output to the screen is a useful example nevertheless! 2.1 prototype int f(int n); formal parameter n of type int return type int usage int res = f(e); expression e that yields an int Expression e will be evaluated and yields an integer value formal parameter n imports this value, the function uses this value to calculate a result, the result is returned and assigned to res. |
API |
17.23 | | 17.25 |
| CALL BY VALUE: REMARKS | 17.25 |
![]() |
2.2 prototype int f(int n); formal parameter n of type int return type int usage int res = f(k); actual variable k of type int This is a special case of the previous one since a variable is a special case of an expression. Formal parameter n imports a copy of the value of the actual variable k, the function uses this value to calculate a result, the result is returned and assigned to res. 2.3 usage int n = f(n); actual variable n of type int This is a special case of the previous one where actaul variable and formal parameter are both called n. Formal parameter n imports a copy of the value of the actual variable n, the function uses this value to calculate a result the result is returned and assigned to the actual variable n. This one is of interest since now the result of the calculation is assigned to the original actual argument and this is the only way in which the actual variable can have its value changed! |
API |
17.24 | | 17.26 |
| CALL BY REFERENCE: DESIGN | 17.26 |
![]() |
For the moment we will use the following conventions: - variables are of type int and of type pointer to int, - formal parameters are represented by lower case letters, and - actual variables are represented by corresponding upper case letters. If function f has a formal parameter of type pointer to int, i.e. int* then its invocation requires the address of an int variable &N declaration void f(int* n); usage f(&N); What happens at calling time? • address &N is copied into formal parameter n and now we have two memory locations n and N such that • n contains the address of N • and N contains an integer value, • *n is an expression that evaluates to the value of N and we say: N and *n are synonomous, • what happens with *n during execution of the function f also happens with the actual variable N since n equals &N. |
API |
17.25 | | 17.27 |
| CALL BY REFERENCE: EXAMPLE | 17.27 |
![]() |
#include<stdio.h> // we use printf() void test(int p, int* q); // prototype int main(void) { int k = 5; printf("before calling test() k is %d.\n", k); // 5 test(k, &k); printf("after calling test() k is %d.\n", k); // 6 } void test(int p, int*q) { printf("inside test(): p = %d and *q = %d.\n", p, *q); // 5 5 printf("now we increase the value of *q.\n"); (*q)++; printf("inside test(): p = %d and *q = %d.\n", p, *q); // 6 6 } |
API |
17.26 | | 17.28 |
| CALL BY REFERENCE: int getInput(void) | 17.28 |
![]() |
1 Get input directly from scanf() int main(void) { int N; printf("an int please.\n>>\t"); // get input scanf("%d", &N); // store at address of N printf("you entered %d.\n", N); // print to stdout } 2 Now using a function int getInput(void); int getInput(void) { int k; // local variable k printf("an int please.\n>>\t"); // user enters 123 scanf("%d", &k); // store 123 at &k return(k); // and return value of k } int main(void) { int N; N = getInput(); // ask user for value // assign value to N printf("you entered %d.\n", N); // print value of N } |
API |
17.27 | | 17.29 |
| CALL BY REFERENCE: void getInput(int*) | 17.29 |
![]() |
3 Now using a call-by-ref function void getInput(int* n); void getInput(int* n) { // n is address of int printf("an int please.\n>>\t"); // user enters 123 scanf("%d", n); // store value at n } NOTA BENE!! scanf("%d", n); AND NOT scanf("%d", &n); int main(void) { int N; getInput(&N); // ask user for value // assign value to N printf("you entered %d.\n", N); // print value of N } |
API |
17.28 | | 17.30 |
| SWAPPING: void swap(int*, int*) | 17.30 |
![]() |
void swap(int* jPtr, int* kPtr) { int temp = *jPtr; *jPtr = *kPtr; *kPtr = temp; } int main(void) { int n1 = 13, n2 = 47; swap(&n1, &n2); return(0); } |
API |
17.29 | | 17.31 |
| SWAPPING: void swap(int*, int*) | 17.31 |
![]() |
void swap(int* jPtr, int* kPtr) { int temp = *jPtr; *jPtr = *kPtr; *kPtr = temp; } |
API |
17.30 | | 17.32 |
| SWAPPING: void swap(int*, int*) | 17.32 |
![]() |
At last |
API |
17.31 | | 17.33 |
| RELATED EXAMPLES: void sort2(int*, int*) | 17.33 |
![]() |
Sort two integer values: void sort2(int* min, int* max) { if (*min > *max) swap(min, max); } Determine minimum and maximum of three integers: Design a function with • three import parameters n1, n2 and n3 • two export parameters *min and *max void minmax(int n1, int n2, int n3, int* min, int* max) { int min23 = ((n2 < n3) ? n2 : n3), max23 = ((n2 > n3) ? n2 : n3); *min = ((n1 < min23) ? n1 : min23); *max = ((n1 > max23) ? n1 : max23); } |
API |
17.32 | | 17.34 |
| RELATED EXAMPLES | 17.34 |
![]() |
Let main() be defined by: int main(void) { int p = 7, q = 13, r = 2, MIN, MAX; minmax(p, q, r, &MIN, &MAX); printf("mini of %d and %d and %d is %d\n", p, q, r, MIN); printf("maxi of %d and %d and %d is %d\n", p, q, r, MAX); } Now what happens here? • the values 7 and 13 and 2 are copied into n1 and n2 and n3 • the addresses &MIN and &MAX are copied into min and max • the smallest value is determined by *min = ...; and stored at address min this address is also known as &MIN since min is a copy of &MIN consequently the value of MIN i.e. *min is now 2 • the largest value is determined by *max = ...; and stored at address max this address is also known as &MAX since max is a copy of &MAX consequently the value of MAX is now 13 |
API |
17.33 | | 17.35 |
| RELATED EXAMPLES: void sort3(int*, int*, int*) | 17.35 |
![]() |
NOTA BENE: • before calling the function minmax the values of MIN and MAX are undefined! • after termination they contain the smalles and largest of p, q and r • in this case min and max are often called pure export parameters. but formal reference parameters can also be used to import data that will be modified and then exported using the same parameters, as in the next example. void sort3(int* min, int* mid, int* max) { sort2(min, mid); // now *min <= *mid sort2(min, max); // now *min <= *max sort2(mid, max); // now *mid <= *max // hence *min <= *mid <= *max } |
API |
17.34 | | 17.36 |
| LAST EXAMPLE: void remquo(int*, int*) | 17.36 |
![]() |
For any two given values n1 and n2 determine remainder and quotient by reference. => Design a function • with two reference parameters *n1 and *n2 that take care of both import and export • addresses of integer values a and b are imported • integer values a%b and a/b are exported using *n1 and *n2, resp. void remquo(int* n1, int* n2) { int rem = *n1 % *n2, quo = *n1 / *n2; // take care!! *n1 = rem; *n2 = quo; } usage: int A = 105, B = 17; remquo(&A, &B); // and now A == 3 and B == 6 Note that the original values 105 and 17 are lost forever! They have been overwritten by the values 3 and 6, respectively. |
API |
17.35 | | 17.01 |