Learning Objectives
At the end of this lecture, you should be able to:
- Define pointers in C and implement basic pointer manipulation operations.
- Define and manipulate void pointers.
- Apply the concepts of pointers to functions through the use of function pointers.
- Apply basic memory management operations through the use of the
malloc
andfree
routines.
Topics
In this lecture, we will cover the following topics:
- C pointers.
- C memory management.
Notes
Basics of C Pointers
- ❓ Let’s say you want to order some pizza, what would you need to give the delivery person other than your order?
- Definition: A pointer in C is a variable that contains the address of
another variable.
- A pointer is your way for ordering pizza in C programming.
- To define a pointer in your code, use the
*
operator in the variable’s declaration.- Example,
int *p
declares a pointerp
that points to another variable of an integer type.- Note that
p
can also be a pointer to an array of integers. In actuality, it points to the first element of the array, the rest of the elements can be accessed by offsetting from that pointer.
- Note that
- Similarly,
double *p2
declares a pointerp2
that points to another variable of typedouble
. - Another example would be
struct person *p3
declares a pointerp3
that points to a structure of typestruct person
.
- Example,
- Given a variable
x
, we can use the&
operator to obtain the address ofx
.- For example,
&x
is the address ofx
in memory, so we could do something like the following to declare a pointer to x:int *p = &x;
- For example,
- To follow a pointer to go to its address – we refer to this operation as
dereferencing a pointer –, we use the
*
operator.- For example,
*p
when used in an expression returns the variable to whichp
points to in memory. So we can write something like:int *p = &x; int y = *p; // now y becomes equal to x
- For example,
- To print a pointer (i.e., the actual address), you can use the
%p
modifier in theprintf
format specifier as follows:printf("%p points to %lf in memory.", p, *p);
Pointers and Types
- Generally, pointers contain the address of a variable of a certain type, that type is typically the one used when declaring the pointer.
- However, we can define
void
pointers, which are pointers that are not associated with a certain data type.- In other words, a
void
pointer can hold the address of a variable of any type.
- In other words, a
- When dereferencing a
void
pointer, you must typecast it into a specific pointer type.- The benefit of using a
void
pointer is that it can point to different things in memory at different times in the code.
- The benefit of using a
- To typecast a pointer, it must be cast a pointer of another type.
- For example,
void *p = &x; int *q = (int *)p;
- For example,
- Here is an example of using and casting a
void
pointer:int x = 3; double y = 3.14; void *p; p = &x; printf("p = %p, *p = %d\n", p, *(int *)p); p = &y; printf("p = %p, *p = %lf\n", p, *(double *)p);
Casting C Pointers
- The
gcc
compiler will not complain if you cast a pointer into another pointer associated with a different type. - For example, given a pointer to an integer
int *p
, it is completely legal to castp
into a pointer to a character (or anything else actually).int *p = &x; char *cptr = (char *)p; printf("p = %p, cptr = %p\n", p, cptr); printf("*p = %d, *cptr = %c\n", *p, *cptr);
- ❓ Can you guess what the outcome of the above program is?
Pointer Arithmetic
- Pointers support operations to move around in memory, this is achieved through
the use of pointer arithmetic.
- You can add or subtract pointer addresses to point to different areas in memory.
- Consider the following array of integers,
int array[] = {1, 2, 3, 4};
.- As a matter of fact,
array
is nothing but an integer pointer that points to the first integer in the memory area that contains the numbers1, 2, 3, 4
. - Therefore, it is legal to define another variable
p
asint *p = array;
and then usep
in the same way you would usearray
.
- As a matter of fact,
- Adding 1 to
array
(or equivalently, top
) corresponds to movingarray
to point to the next (or second) integer in the memory area that contains the array.- In other words,
int *p2 = array + 1
is an integer pointer that points to the second element in the array, which is the constant2
.
- In other words,
-
🏃 What would the expression
(array + 2) == &(array[2])
evaluate to? true of false? - Similarly, consider the character array
char carray[] = {'a', 'b', 'c'};
- We can define the character pointer
c
aschar *c = carray;
- We can define the character pointer
-
Adding 1 to
carray
would evaluate to a pointer to the second element (i.e.,b
) in the original array. - 🏃 Assume that we have a pointer
p
to an array of integers that contains{1, 2, 3, 4}
, and let’s assume thatp
contains the address0x00000004
.- Let’s define another pointer
p2
asint *p2 = p + 1
, what would be the address contained inp2
?
- Let’s define another pointer
-
🏃 Repeat the previous exercise assuming
p
is a pointer to an array of characters, what would be the address contained inp + 1
? - General Rule: Adding
1
to an integer pointerp
is equivalent to addingsizeof(int)
bytes to the address contained inp
. - Similarly, adding
1
to a character pointerc
is equivalent to addingsizeof(char)
bytes to the address contained inc
.
C Memory Management
- 🏃 Consider the following C code:
int *p; printf("p points to the value %d in memory\n", *p);
- What would happen when we run the above program?
Allocating Memory
- So we need a way to initialize a pointer.
- One way to do it is to assign it to the address of an already existing
variable as follows:
int x = 3; int *p = &x;
- But that is very limiting, we need a way to allocate memory (i.e., ask the
operating system for some memory) and save the address returned in a pointer.
- The way to do that is using the
malloc
function.
- The way to do that is using the
- The signature of the
malloc
function is as follows:void *malloc(size_t size);
- where
size
is the number of bytes you would like the operating system to allocate for you.
- where
- Using your terminal, type
man malloc
to check the documentation for themalloc
function.
Deallocating Memory
- When we are done using a piece of memory, we must return this piece of memory to the OS so that it can allocate it to someone else.
- Therefore, given an allocated pointer
p
, usefree(p)
to free the memory area thatp
points to and return it to the OS. - The signature of the
free
function is as follows:void free(void *ptr);
-
Using your terminal, type
man free
to check the documentation for thefree
function. - 🏃 Why is it bad to do the following:
int x = 3; int *p = &x; free(p);
- NOTE: We are abusing notation a bit here by saying that
malloc
andfree
are handled by the operating system. In fact, memory management happens in user-space and is supported by OS-provided system calls.- You will implement a memory manager in the second xv6 assignment.
The C Memory Commandments
- Whoever malloc’eth shall free’th what they have malloced.
- In other words, don’t free someone else’s memory.
- Check
malloc
’s return value, you might be out of memory. malloc
returns a chunk of memory that contains garbage.- Do not make any assumptions about its content!
- Freed memory should never be accessed again.
- Double freeing memory is not good.
- This is not the place for charity.
Function Pointers
- Similar to variables, functions in C have addresses in memory.
- So we can define pointers to functions the same way we can define pointers to variables.
- A function pointer is a variable that contains the address of a function in your code.
- To define a function pointer, we can use the following syntax:
return_type (*function_ptr_name)(arguments);
- For example, the following piece of code defines a function pointer
foo
that points to a function that returns anint
and accepts threeint
arguments.int (*foo)(int, int, int);
- To assign a function pointer to an already existing function, you can
equivalently use the
&
operator or simply assign it to the function’s name.int bar(int a, int b, int c) { return a + b + c; } int (*foo)(int, int, int); /* The following two statements are equivalent */ foo = &bar; foo = bar;
- To call the function that
foo
points to, we simply dereference the pointer and pass the arguments to it.int r = (*foo)(1, 2, 3);