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
mallocandfreeroutines.
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 *pdeclares a pointerpthat points to another variable of an integer type.- Note that
pcan 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 *p2declares a pointerp2that points to another variable of typedouble. - Another example would be
struct person *p3declares a pointerp3that points to a structure of typestruct person.
- Example,
- Given a variable
x, we can use the&operator to obtain the address ofx.- For example,
&xis the address ofxin 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,
*pwhen used in an expression returns the variable to whichppoints 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
%pmodifier in theprintfformat 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
voidpointers, which are pointers that are not associated with a certain data type.- In other words, a
voidpointer can hold the address of a variable of any type.
- In other words, a
- When dereferencing a
voidpointer, you must typecast it into a specific pointer type.- The benefit of using a
voidpointer 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
voidpointer: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
gcccompiler 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 castpinto 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,
arrayis 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
pasint *p = array;and then usepin the same way you would usearray.
- As a matter of fact,
- Adding 1 to
array(or equivalently, top) corresponds to movingarrayto point to the next (or second) integer in the memory area that contains the array.- In other words,
int *p2 = array + 1is 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
caschar *c = carray;
- We can define the character pointer
-
Adding 1 to
carraywould evaluate to a pointer to the second element (i.e.,b) in the original array. - 🏃 Assume that we have a pointer
pto an array of integers that contains{1, 2, 3, 4}, and let’s assume thatpcontains the address0x00000004.- Let’s define another pointer
p2asint *p2 = p + 1, what would be the address contained inp2?
- Let’s define another pointer
-
🏃 Repeat the previous exercise assuming
pis a pointer to an array of characters, what would be the address contained inp + 1? - General Rule: Adding
1to an integer pointerpis equivalent to addingsizeof(int)bytes to the address contained inp. - Similarly, adding
1to a character pointercis 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
mallocfunction.
- The way to do that is using the
- The signature of the
mallocfunction is as follows:void *malloc(size_t size);- where
sizeis the number of bytes you would like the operating system to allocate for you.
- where
- Using your terminal, type
man mallocto check the documentation for themallocfunction.
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 thatppoints to and return it to the OS. - The signature of the
freefunction is as follows:void free(void *ptr); -
Using your terminal, type
man freeto check the documentation for thefreefunction. - 🏃 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
mallocandfreeare 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. mallocreturns 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
foothat points to a function that returns anintand accepts threeintarguments.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
foopoints to, we simply dereference the pointer and pass the arguments to it.int r = (*foo)(1, 2, 3);