CS 11 C track: lecture 5
n Last week: pointers
n This week:
n Pointer arithmetic
n Arrays and pointers
n Dynamic memory allocation
n The stack and the heap
Pointers (from last week)
n Address: location where data stored
n Pointer: variable that holds an address
int i = 10;
int *j = &i;
int k = 2 * (*j); /* dereference j */
Pointer arithmetic (1)
n Can add/subtract integers to/from pointers
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr; /* (*p) == ? */
p++; /* (*p) == ? */
p += 2; /* (*p) == ? */
p -= 3; /* (*p) == ? */
Pointer arithmetic (2)
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr; /* (*p) == ? */
1 2 3 4 5
arr
p
Pointer arithmetic (3)
p++; /* (*p) == ? */
1 2 3 4 5
arr
p
Pointer arithmetic (4)
p += 2; /* (*p) == ? */
1 2 3 4 5
arr
p
Pointer arithmetic (5)
p -= 3; /* (*p) == ? */
1 2 3 4 5
arr
p
Pointer arithmetic (6)
Let's try that using
addresses only...
Pointer arithmetic (7)
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr; /* (*p) == ? */
0x1234
1 2 3 4 5
arr 0x1234
p 0x1234
Pointer arithmetic (8)
p++; /* (*p) == ? */
0x1234
1 2 3 4 5
arr 0x1234
p 0x1238 (assume 4 byte integers)
Pointer arithmetic (9)
p += 2; /* (*p) == ? */
0x1234
1 2 3 4 5
arr 0x1234
(0x1240 = 0x1234 + 0x0c;
p 0x1240
0x0c == 12 decimal or 3x4)
Pointer arithmetic (10)
p-= 3; /* (*p) == ? */
0x1234
1 2 3 4 5
arr 0x1234
p 0x1234
Pointer arithmetic (11)
n Get size of a type using the sizeof
operator:
printf("size of integer: %d\n",
sizeof(int));
printf("size of (int *): %d\n",
sizeof(int *));
n N.B. sizeof is not a function
n takes a type name as an argument!
Pointer arithmetic (12)
n N.B. pointer arithmetic doesn't add/
subtract address directly but in multiples
of the size of the type in bytes
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr;
p++; /* means: p = p + sizeof(int);*/
Pointer arithmetic (13)
p++; /* (*p) == ? */
0x1234
1 2 3 4 5
arr 0x1234
(j = 0x1234 + sizeof(int) = 0x1238,
p 0x1238
not 0x1235)
Arrays and pointers (1)
n Arrays are pointers in disguise!
n Arrays: "syntactic sugar" for pointers
int arr[] = {1, 2, 3, 4, 5};
printf("arr[3] = %d\n", arr[3]);
printf("arr[3] = %d\n", *(arr + 3));
n arr[3] and *(arr + 3) are identical!
n arr is identical to &arr[0]
Arrays and pointers (2)
n Can use pointer arithmetic wherever we
use array operations; consider this:
int i;
double array[1000];
for (i = 1; i < 999; i++) {
array[i] = (array[i-1] +
array[i] + array[i+1]) / 3.0;
}
Arrays and pointers (3)
n Exactly the same as:
int i;
double array[1000];
for (i = 1; i < 999; i++) {
*(array+i) = (*(array+i-1) +
*(array+i) + *(array+i+1)) / 3.0;
}
Arrays and pointers (4)
n When you say *(array + i), you have
to add i to array and dereference
n For large values of i, this is relatively slow
n Incrementing pointers by 1 is faster than
adding a large number to a pointer
n Can use this fact to optimize the preceding
code in an interesting way
Arrays and pointers (5)
double array[1000];
double *p1, *p2, *p3;
p1=array; p2=array+1; p3=array+2;
for (i = 1; i < 999; i++) {
*p2 = (*p1 + *p2 + *p3) / 3.0;
p1++; p2++; p3++;
}
Arrays and pointers (6)
array
...
Add *p1, *p2, *p3
p1 together, divide by 3,
p2
put result into *p2
p3
Arrays and pointers (7)
array
...
Increment *p1, *p2, *p3
p1 by 1 each, continue
p2
p3
Arrays and pointers (8)
n We replaced 3 pointer additions with
three pointer increments, which are
usually faster
n Even more significant for 2-d arrays
Dynamic memory allocation (1)
Recall that we can't do this:
n
int n = 10;
int arr[n]; /* not legal C */
n However, often want to allocate an array
where size of array not known in advance
n This is known as "dynamic memory
allocation"
n dynamic as opposed to "static" (size known at
compile time)
Dynamic memory allocation (2)
n Let's say we want to allocate memory for
e.g. arrays "on the fly"
n Later will have to deallocate memory
n Three new library functions for this:
n void *malloc(int size)
n void *calloc(int nitems, int size)
n void free(void *ptr)
n All found in <stdlib.h> header file
void *
n What does void * mean?
n It's a "pointer to anything"
n Actual type either doesn't matter or will be
given later by a type cast
n malloc/calloc return void *
n free takes a void * argument
Using malloc() (1)
n malloc()stands for "memory allocator"
n malloc() takes one argument: the size of the
chunk of memory to be allocated in bytes
n recall: a byte == 8 bits
n an int is 32 bits or 4 bytes
n malloc() returns the address of the chunk of
memory that was allocated
Using malloc() (2)
n malloc()is often used to dynamically allocate
arrays
n For instance, to dynamically allocate an array of
10 ints:
int *arr;
arr = (int *) malloc(10 * sizeof(int));
/* now arr has the address
of an array of 10 ints */
Using calloc() (1)
n calloc()is a variant of malloc()
n calloc() takes two arguments: the number of
"things" to be allocated and the size of each
"thing" (in bytes)
n calloc() returns the address of the chunk of
memory that was allocated
n calloc() also sets all the values in the allocated
memory to zeros (malloc() doesn't)
Using calloc() (2)
n calloc()is also used to dynamically allocate
arrays
n For instance, to dynamically allocate an array of
10 ints:
int *arr;
arr = (int *) calloc(10, sizeof(int));
/* now arr has the address
of an array of 10 ints, all 0s */
malloc/calloc return value (1)
n malloc and calloc both return the address of
the newly-allocated block of memory
n However, they are not guaranteed to succeed!
n maybe there is no more memory available
n If they fail, they return NULL
n You must always check for NULL when using
malloc or calloc
n We sometimes leave it out here for brevity
malloc/calloc return value (2)
n bad:
int *arr = (int *) malloc(10 * sizeof(int));
/* code that uses arr... */
n good:
int *arr = (int *) malloc(10 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "out of memory!\n");
exit(1);
}
n Always do this!
malloc() vs. calloc()
n malloc/calloc both allocate memory
n calloc has slightly different syntax
n as we've seen
n Most importantly: calloc() zeros out
allocated memory, malloc() doesn't.
n calloc() a tiny bit slower
n I prefer calloc()
Using free() (1)
n malloc() and calloc() return the address of
the chunk of memory that was allocated
n Normally, we store this address in a pointer
variable
n When we have finished working with this chunk of
memory, we "get rid of it" by calling the free()
function with the pointer variable as its argument
n This is also known as "deallocating" the memory
or just "freeing" it
Using free() (2)
int *arr;
arr = (int *) calloc(10, sizeof(int));
/* now arr has the address
of an array of 10 ints, all 0s */
/* Code that uses the array... */
/* Now we no longer need the array, so "free"
it: */
free(arr);
/* Now we can't use arr anymore. */
Using free() (3)
n NOTE: When we free() some memory, the
memory is not erased or destroyed
n Instead, the operating system is informed that we
don't need the memory any more, so it may use it
for e.g. another program
n Trying to use memory after freeing it can cause a
segmentation violation (program crash)
Dynamic memory allocation (3)
#include <stdlib.h>
int *foo(int n) {
int i[10]; /* memory allocated here */
int i2[n]; /* ERROR: NOT VALID! */
int *j;
j = (int *)malloc(n * sizeof(int));
/* Alternatively: */
/* j = (int *)calloc(n, sizeof(int)); */
return j;
} /* i’s memory deallocated here; j’s not */
Dynamic memory allocation (4)
void bar(void) {
int *arr = foo(10);
arr[0] = 10;
arr[1] = 20;
/* ... do something with arr ... */
free(arr); /* deallocate memory */
}
n Not calling free() leads to memory leaks !
Memory leaks (1)
void leaker(void) {
int *arr = (int *)malloc(10 * sizeof(int));
/* Now have allocated space for 10 ints;
* do something with it and return without
* calling free().
*/
} /* arr memory is leaked here. */
n After leaker() returns, nothing points to
memory allocated in the function à memory leak
Memory leaks (2)
void not_leaker(void) {
int *arr = (int *)malloc(10 * sizeof(int));
/* Now have allocated space for 10 ints;
* do something with it.
*/
free(arr); /* free arr's memory */
} /* No leak. */
n Here, we explicitly free() the memory allocated
by malloc() before exiting the function.
Memory leaks (3)
void not_leaker2(void) {
int arr[10];
/* Now have allocated space for 10 ints;
* do something with it.
*/
} /* No leak. */
n Here, we don't have to free() the memory,
since it was allocated locally (on the "stack").
n "What's the stack?" (you may ask...)
Memory leaks (4)
void crasher(void) {
int arr[10];
/* Now have allocated space for 10 ints;
* do something with it.
*/
free(arr); /* BAD! */
}
n Here, we free() memory we don't need to free!
n Anything can happen (e.g. core dump)
Memory leaks (5)
n Rules of thumb:
n 1) Any time you allocate memory using malloc()
or calloc(), you must eventually call free() on
that memory
n 2) You must free() the exact same pointer
(address) that was returned from malloc() or
calloc()
n 3) You don't have to free() the memory in the
same function as the one where malloc/calloc
was called
The stack and the heap (1)
n Local variables, function arguments, return value
are stored on a stack
n Each function call generates a new "stack frame"
n After function returns, stack frame disappears
n along with all local variables and function
arguments for that invocation
The stack and the heap (2)
int contrived_example(int i, float f)
{
int j = 10;
double d = 3.14;
int arr[10];
/* do some stuff, then return */
return (j + i);
}
The stack and the heap (3)
/* somewhere in code */
int k = contrived_example(42, 3.3);
n What does this look like on the stack?
The stack and the heap (4)
(more frames)
return value 52
function i = 42
arguments f = 3.3
j = 10 stack frame
local d = 3.14 for
contrived_example
variables arr[10] = (42, 3.3)
<garbage>
The stack and the heap (5)
n Another example:
int factorial(int i)
{
if (i == 0) {
return 1;
} else {
return i * factorial (i - 1);
}
}
The stack and the heap (6)
n Pop quiz: what goes on the stack for factorial
(3)?
n For each stack frame, have...
n no local variables
n one argument (i)
n one return value
n Each recursive call generates a new stack frame
n which disappears after the call is complete
The stack and the heap (7)
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (8)
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (9)
return value ?
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (10)
return value ?
factorial(0) stack frame
i = 0
return value ?
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (11)
return value 1
factorial(0) stack frame
i = 0
return value ?
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (12)
return value 1
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (13)
return value 2
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (14)
return value 6
factorial(3) stack frame
i = 3
The stack and the heap (15)
factorial(3)
result: 6
The stack and the heap (16)
void foo(void) {
int arr[10]; /* local (on stack) */
/* do something with arr */
} /* arr is deallocated */
n Local variables sometimes called "automatic"
variables; deallocation is automatic
The stack and the heap (17)
foo
local arr[10] =
stack frame
variables <whatever>
for foo()
The stack and the heap (18)
n The "heap" is the general pool of computer memory
n Memory is allocated on the heap using malloc() or
calloc()
n Heap memory must be explicitly freed using free()
n Failure to do so à memory leak!
The stack and the heap (19)
void foo2(void) {
int *arr;
/* allocate memory on the heap: */
arr = (int *)calloc(10, sizeof(int));
/* do something with arr */
} /* arr is NOT deallocated */
The stack and the heap (20)
void foo3(void) {
int *arr;
/* allocate memory on the heap: */
arr = (int *)calloc(10, sizeof(int));
/* do something with arr */
free(arr);
}
The stack and the heap (21)
0x1234 arr[0]
arr[1]
arr[2]
arr[3]
foo2 and arr[4]
foo3
(etc.)
heap
local arr =
0x1234 stack frame
variables
stack
The stack and the heap (22)
0x1234 arr[0]
arr[1]
(after foo2
exits, arr[2]
without arr[3]
memory
freeing arr[4]
leak
memory)
(etc.)
heap
stack
The stack and the heap (23)
0x1234 arr[0]
arr[1]
(after foo3
exits, with arr[2]
freeing arr[3]
memory) arr[4]
(etc.)
heap
stack
Memory leaks
n Memory leaks are one of the worst kinds of bugs
n often, no harm done at all
n eventually may cause long-running program to crash
n out of memory
n very hard to track down
n Special tools (e.g. valgrind) exist to debug
memory leaks
n I supply you with a very simple leak checker
Next week
n struct
n typedef
n Linked lists