SYSC 2006 C Winter 2012 Pointers and Arrays Copyright D. Bailey, Systems and Computer Engineering, Carleton University updated Sept. 21, 2011, Oct.18, 2011,Oct. 28, 2011, Feb. 25, 2011
Memory Organization Memory can be viewed as a collection of consecutively-numbered cells usually, each cell holds an 8-bit byte a char is stored in one cell a 32-bit int is stored in 4 adjacent cells on smaller machines, values of type int are 16 bits wide and are stored in pairs of adjacent cells The cell numbers are known as addresses
Variables and Pointers A variable is a symbolic name for a group of cells # of cells depends on the variable's type Pointer: a variable that contains the address of a variable
Pointer Variable Declarations int *p; This declares a variable named p, whose type is pointer to int type * means pointer to type Common error: thinking that this statement declares a variable named *p, whose type is int
Address-of (&) Operator Unary operator & yields the address of its operand int x = 1, y = 2; p = &x; This assigns the address of x to variable p p now points to x p 1 x 2 y
Dereferencing (*) Operator Unary operator * dereferences the pointer y = *p; *p yields the value in the variable pointed to by p, which is 1 y is assigned 1 p 1 x 1 y
Dereferencing (*) Operator *p = 0; the variable pointed to by p is assigned 0 x now contains 0 p 0 x 1 y
Dereferencing (*) Operator If p points to x, then *p can be used anywhere x can be used *p = *p + 10; is equivalent to x = x + 10; x now contains 10 p 10 x 1 y
Precedence Unary & and * have higher precedence than the arithmetic, comparison and logical operators y = *p + 1; takes the value pointed to by p, adds 1, then assigns the result to y This statement means something completely different: y = *(p + 1); we'll learn what it means, later
Pointer Assignment Pointer variables can be used without dereferencing them; e.g., int *p2; p2 = p; copies the contents of p into p2 p2 and p now point to the same variable p 10 x p2 1 y
Pointers and Functions A function that is supposed to swap its arguments (but it doesn't) void swap(int x, int y) { int temp; } temp = x; x = y; y = temp;
Pointers and Functions Typical call: int a = 5, b = 10; swap(a, b); When the function returns, a and b are unchanged Draw a memory diagram depicting the stack frames of swap and its caller, to explain why this function doesn't do what it's supposed to
Pointers and Functions What's wrong? call-by-value: parameters x and y are distinct from a and b The function arguments must instead be pointers to the variables that are to be swapped: int a = 5, b = 10; swap(&a, &b);
Pointers and Functions We have to revise the function so that the parameters are pointers to integers void swap(int *px, int *py) { int temp; } temp = *px; *px = *py; *py = temp;
Pointers and Functions Draw a memory diagram depicting the stack frames of swap and its caller, to explain what this function does
Pointers and Arrays The declaration: int a[10]; defines an array that can store 10 integers int *pa; pa = &a[0]; pa now points to element 0 in the array
Pointers and Arrays The name of an array is a synonym for the address of its zero'th element This means that this: pa = &a[0]; can be rewritten as: pa = a;
Very Important Fact! An array is not a pointer An array is not a pointer An array is not a pointer An array is not a pointer An array is not a pointer An array is not a pointer An array is not a pointer An array is not a pointer
Functions and Arrays When an array name is used as a function argument, C passes the address of the zero'th element to the function; e.g., void normalize(int x[], int n); int samples[100]; C converts this call: normalize(samples, 50); to: normalize(&samples[0], 50);
Functions and Arrays void normalize(int x[], int n) {... for (i = 0; i < n; i++) { x[i] =... }... } Parameter x is actually a pointer to an array of ints
Functions and Arrays So, we can change the declaration of the parameter to "pointer to int" (int *) void normalize(int *px, int n) {... for (i = 0; i < n; i++) { px[i] =... }... } So, why is px[i] =... o.k.?
Array Access via Pointers Consider (again): int v = 10; int a[10]; int *pa; pa = a; // equiv. to: pa = &a[0]; These statements all copy the contents of v into a[0]: a[0] = v; // O.K... *pa = v; // O.K... pa[0] = v; // Really? Yes!
Array Access via Pointers If pa points to a particular array element, then pa + 1 points to the next element So, if pa contains &a[0] *pa = v; is equivalent to a[0] = v; *(pa + 1) = v; is equivalent to a[1] = v; *(pa + 2) = v; is equivalent to a[2] = v; *(pa + i) = v; is equivalent to a[i] = v;
Array Access via Pointers If a is an array, an expression of the form a[i] is equivalent to *(&a[0] + i), which can be shortened to *(a + i) Similarly, if pa contains &a[0], then *(pa + i) can be rewritten as pa[i]
Array Access via Pointers So, if pa contains &a[0] pa[0] = v; is equivalent to *pa = v; pa[1] = v; is equivalent to *(pa + 1) = v; pa[2] = v; is equivalent to *(pa + 2) = v; pa[i] = v; is equivalent to *(pa + i) = v;
average - array-and-index version double average(int data[], int n) { double sum = 0; int i; } for(i = 0; i < n; i++) { sum = sum + data[i]; } return sum / n; We can rewrite this to use pointers
average - pointer-and-offset version double average(int *data, int n) { double sum = 0; int i; } for(i = 0; i < n; i++) { sum = sum + *(data + i); // or, sum = sum + data[i]; } return sum / n;
Pointer arithmetic Pointers are variables, so we can add and subtract values from them; e.g., pa = pa + 1; increments pa to point to the next array element Common idioms: ++pa; or pa++; Revise average once more
average - walking pointer" version double average(int *data, int n) { double sum = 0; int i; } for(i = 0; i < n; i++) { sum = sum + *data; ++data; } return sum / n;
Allocating Memory at Run-time Allocate a block of memory big enough to hold a value of type int #include <stdlib.h>... int *p; p = malloc(sizeof(int)); if (p!= NULL) { *p = 3; } else { /* error - malloc failed */ }
Allocating Memory at Run-time malloc is declared in stdlib.h sizeof(type-name) returns the amount of memory (in bytes) required to hold a value of the specified type malloc(size) allocates a block of memory of the specified size (bytes), from the heap it returns a pointer to the allocated memory block if memory cannot be allocated, NULL is returned
assert() Declared in assert.h Syntax: assert(condition); condition specifies what must be true for program execution to continue If the condition is false, the program terminates with an error message
Allocating Memory at Run-time Every time you call malloc, you should call assert to verify that memory was allocated #include <stdlib.h> #include <assert.h> int *p; p = malloc(sizeof(int)); assert(p!= NULL); // Here, we know that p points to // a block allocated from the heap. *p = 3;
Allocating Arrays at Run-time Write a function that allocates an array with a specified capacity, and returns a pointer to the array The function should cause the program to terminate if the capacity is <= 0 or if memory cannot be allocated
Allocating Memory at Run-time #include <stdlib.h> #include <assert.h> int *alloc_intarray(int capacity) { int *pa; } assert(capacity > 0); /* B */ pa = malloc(capacity * sizeof(int)); assert(pa!= NULL); /* C */ return pa;
Allocating Memory at Run-time #include <stdlib.h> #include <assert.h> int *a; int capacity = 100; /* A */ a = alloc_intarray(capacity); // we know a is not NULL - why?... /* D */ for (int i = 0; i < capacity; i++){ a[i] = 0; }
Memory Diagrams Draw the stack frames and the heap after the statements at points A, B, C and D have been executed.