Pointers & Pass-by-reference
A pointer is a value that designates the address of some value.
Pointers are variables that hold a memory location as their value
Remember the address-of operator (&) we used with scanf()?
This operator gets the address of the operand
Pointers give us a way to save the result of the address-of operator into a variable
The type returned by the address-of is a pointer!
Assigning and dereferencing pointers
Instead of passing the result of the address-of operator to a function (e.g.
scanf
) let’s save the
result into a variable
1 int a = 5; // declare & initialize an int to the value of 5
2 int *b = &a; // declare & initialize a pointer to store the address
of the variable a
At this point,
b
doesn’t store the value 5. It stores the memory address of the variable
a
. The
variable a stores the value 5, not b.
But how can we access the value of a using the pointer b?
We use the
dereferencing operator
to tell the computer "take me to the memory location
stored by this pointer:
1 int a = 5; // declare & initialize an int to the value of 5
2 int *b = &a; // declare & initialize a pointer to store the address
of the variable a
3 int c = *b; // declare & initialize an int to the value of the
dereferenced b, which is the value stored by a
The dereference operator is the complement to the address-of operator, similar to how subtrac-
tion is the complement to addition
House analog y
We are all familiar with houses and the address system we use with the post oice
This is a great parallel to pointers in C.
We can think of variables as houses (a very large box to store data in - but we won’t worr y about
the size of the house right now).
We can think of memory address as addresses
1
Do addresses have houses of their own? NO!
But when we declare a pointer, we make a house specifically to store an address
Some questions (credit, though this is for C++ http://alumni.cs.ucr.edu/~pdiloren/C++_Pointers/
wherelive.htm):
Where do you live? (&)
Suppose we have the following code:
1 int paul = 21; // store the value 21 in paul's house
2 int tom = paul; // store the value in paul's house in tom's house
(makes a copy)
3 int *melissa = &paul; // store address of paul in melissa's house
And suppose paul’s address is 1500.
What is the value stored in melissas house?
1500
melissa’s house stores a pointer
Let’s look at this as a diagram:
2
Figure 1: IMAGE
What’s in your house? (*)
Suppose we continue our example above and write the following:
1 int dave = *melissa; // stores the value 21 in dave's house
How did 21 get into daves house?
Dave asks melissa what value she is storing.
Melissa tell’s dave “1500”.
Dave knows melissas house stores a pointer, so he then goes to the address 1500 and ask
whoever is there what value is inside (notice, dave doesn’t know that 1500 is paul’s house)
Dave then stores 21 in his house
3
Figure 2: IMAGE
Now suppose we execute the following line:
1 *melissa = 30;
How do the houses update?
paul’s house is updated to store 30
melissa’s house stays the same
dave’s house stays the same
*
dave lives in a dierent house than paul, and the contents of dave’s house don’t change
when the contents of paul’s house change
NULL pointers
For most variable types, we have a default value we typically use by default. For instance, 0 is
the default type for int.
Pointers have no explicit default type (meaning will value will be garbage if you do not initialize
the pointer when you declare it).
4
We use a special marco (preprocessor definition) called
NULL
to indicate that this pointer does
not point to any memory address:
1 int *ptr = NULL; // does not point to anything
2 //now we can check if the pointer is safe to dereference (because it
actually points to something)
3 if(ptr != NULL){
4 *ptr = 5; // safe to dereference
5 }
If we don’t make sure we properly initialize a pointer to a memory address
Stress-testing your understanding of pointers:
What if we wanted a pointer to a pointer that points to an int?
This means the data type of this variable/house would point to a memory address that
points to the memory address of an int
1 int a = 5;
2 int *ptr = &a;
3 int **ptr2ptr = &ptr;
We can continue doing this over and over to get “deeper” into what points to what
Consider this complicated example:
1 int *p1, *p2, **p3, a = 5, b = 10;
2 p1 = &a;
3 p2 = &b;
4 p3 = &p2;
5 *p1 = 10;
6 p1 = p2;
7 *p1 = 20;
8 **p3 = 0;
9 printf("%i %i %i %i %i\n", *p1, *p2, **p3, a, b);
10 Answer:
11 0 0 0 10 0
Arrays and pointers
Arrays represent contiguous blocks of computer memory. Each element of an array is placed
immediately next to the preceding/next element of the array in memory.
5
Pointers and arrays are deeply and somewhat confusingly linked. There’s two basic rules:
1.
A variable declared as an array of some type acts as a pointer to that type. When used by itself, it
points to the first element of the array.
2.
A pointer can be indexed like an array name. We can use
[]
with pointers the same way we use
array names.
Array names can be thought of as constant pointers, meaning the address the y store cannot
change, but the contents at that address can change
int *const const_ptr creates a constant pointer to a non-constant int
There’s a niy trick called the ‘backwards spiral rule’ that makes reading these declarations
a lot easier (you don’t need to know/study this, just providing for additional info) http://c-
faq.com/decl/spiral.anderson.html
Figure 3: IMAGE
Arrays as pointers
This occurs primarily when arrays are passed into/returned from functions (remember how we
returned an array from a function? We used a pointer).
6
1 /* two equivalent function definitions */
2 int func(int *paramN);
3 int func(int paramN[]);
Pointers and array names can be used almost interchangeably. There are a few exceptions/things
to keep in mind:
1.
You cannot assign a new pointer value to an array name (since the array name is a constant
value, and therefore immutable/non-modifiable).
2. The array name will always point to the first element of the array.
Pointer arithmetic
We can add/subtract integer values from pointers. This is called pointer arithmetic.
This is relevant for iterating over arrays using a pointer and pointer arithmetic
The following two expressions are equivalent:
1 *(arr+j) // access element using pointer arithmetic
2 arr[j]; // access element using [] operator
What does the first expression do?
Adds j*sizeof(arr type) to arr, and then dereferences that memory location
For instance, if we have an array of ints, each array element is 4 bytes long.
If
arr
starts at address 3500, the 5th element is located at memory address
3500+(5*sizeof(int)).
Notice the
sizeof()
was not explicit, the compiler will automatically multiply
j
by the
size of each member of the array
Consider the following (figures, etc taken from here):
1 int *ip;
2 int a[10];
3 ip = &a[3];
ip would end up pointing to the forth element of a.
Figure 4: IMAGE
Now suppose we wrote
7
1 ip2 = ip + 1;
Then we’d have:
Figure 5: IMAGE
Knowledge check
1 #include <stdio.h>
2
3 int main()
4 {
5 int array [5] = { 9, 7, 5, 3, 1 };
6
7 printf("%p\n", (void*) &array[1]); // print memory address of array
element 1, must cast to void pointer to print
8 printf("%p\n", (void*) array+1); // print memory address of array
pointer + 1
9
10 printf("%d\n", array[1]); // prints 7
11 printf("%d\n", *(array+1)); // prints 7 (note the parenthesis
required here)
12
13 return 0;
14 }
Iteration using pointer arithmetic
We can use pointer arithmetic to iterate over an array, instead of using integer indices
1 const size_t arr_len = 7;
2 char name[arr_len] = "Mollie";
3 int numVowels(0);
4 // initialize the pointer to the beginning of the array
8
5 // condition is whether or not the pointer has past the last valid
memory address for the array (name + arr_len)
6 // loop statement incrementing the pointer to the next element in the
array
7 for (char *ptr = name; ptr < name + arr_len; ++ptr)
8 {
9 switch (*ptr)
10 {
11 case 'A':
12 case 'a':
13 case 'E':
14 case 'e':
15 case 'I':
16 case 'i':
17 case 'O':
18 case 'o':
19 case 'U':
20 case 'u':
21 ++numVowels;
22 }
23 }
Pass-by-reference
Basic idea: instead of copying arguments to a function, use the same underlying memory location
to pass values into a function (i.e. instead of duplicating a house when calling a function, use the
same house).
Many other languages support a concept called passing-by-reference
C always uses pass-by-value, which means when we write:
1 int dummy_func(int param){
2 // this modification doesn't affect the variable that was passed into
the function
3 param++;
4 return param;
5 }
6
7 int main(){
8 int a = 5;
9 int b = dummy_func(a); // a is copied to dummy_func
10 // since a was copied (and then the copied value was modified in
dumm_func, then returned), the value of a in main does not change
9
11 printf("a: %d, b: %d\n", a, b);
12 }
Pass-by-reference prevents the value from being copied and instead tells the function to directly
modify the variable stored in the caller’s scope
This is clearly useful!
So far, we’ve only been able to return a single data type, but if we can modify parameters in
the caller’s scope, we have a way to “return multiple values by telling the parameters “not
to copy” into the functions scope.
But C does not support this.
Fortunately, pointers are just memory addresses.
If you copy a pointer, the memory location says the same.
This means we can create pass-by-reference behavior by passing pointers to functions
The pointers are copied into the function, but if we dereference and modify their value, we
aren’t changing the pointer, but the contents the pointer refers to.
This is essentially pass-by-reference behavior
In the notes on arrays, we actually never needed to return the array! For instance:
1 //NOTICE: the asterisk (star) next to int indicates we are returning an
array
2 int* add_to_zeroth_element(int arr[], size_t arr_len, int value){
3 // this is just a dummy array operation, in practice you'll do
wonderful and amazing things here
4 arr[0] += value;
5 // NOTICE: return the array, we don't use [] here, just the name of
the array.
6 return arr;
7 }
8
9 void add_to_zeroth_element_no_return(int *const arr, size_t arr_len,
int value){
10 // this is just a dummy array operation, in practice you'll do
wonderful and amazing things here
11 arr[0] += value;
12 // don't need
13 }
14
15 int main(){
16 int arr[] = {1,2,3};
17 // notice the type here has to match the return type of the function.
Exactly what's going on here will be covered with pointers.
10
18 int* result = add_to_zeroth_element(arr, 3, 5);
19
20 for (j = 0; j < 3; ++j)
21 {
22 printf("%d ", arr[j]);
23 }
24
25 // increment once more on the first element, no return
26 add_to_zeroth_element_no_return(arr, 3, 5);
27
28 for (j = 0; j < 3; ++j)
29 {
30 printf("%d ", arr[j]);
31 }
32 }
Exercises
1.
Write a program in C to add two numbers using pointers. Test Data : Input the first number : 5
Input the second number : 6 Expected Output : The sum of the entered numbers is : 11
1 #include <stdio.h>
2 int main()
3 {
4 int first, second, *ptr, *qtr, sum;
5
6 printf(" Input the first number : ");
7 scanf("%d", &first);
8 printf(" Input the second number : ");
9 scanf("%d", &second);
10
11 ptr = &first;
12 qtr = &second;
13
14 sum = *ptr + *qtr;
15
16 printf(" The sum of the entered numbers is : %d\n\n",sum);
17
18 return 0;
19 }
2. Write a program in C to print the elements of an array in reverse order using pointers
11
1 #include <stdio.h>
2 int main()
3 {
4 int n, i, arr[15];
5 int *ptr;
6
7 printf(" Input the number of elements to store in the array (max 15)
: ");
8 scanf("%d",&n);
9 ptr = &arr[0]; // ptr stores the address of base array arr1
10 printf(" Input %d number of elements in the array : \n",n);
11 for(i=0; i<n; i++)
12 {
13 printf(" element - %d : ",i+1);
14 scanf("%d",ptr);//accept the address of the value
15 ptr++;
16 }
17
18 // print the contents
19 for (ptr = arr + n - 1; ptr >= arr; ptr--){
20 printf("%d ", *ptr);
21 }
22 printf("\n");
23 }
3.
Create a function
print_addr(int x)
whose sole purpose is to print the address of the integer
x
passed to it. Create an integer variable in
main
, print out its address, and then pass that variable
to print_addr. Compare the results. Is this expected behavior?
12