Jump to content

C Programming/Pointers

From Wikiversity

Objective

[edit | edit source]

This lecture will give you knowledge on:

  • Pass by value, pass by reference
  • Definition of reference and pointer, why using pointer is necessary ?
  • Declaring pointers, dereferencing pointers
  • Application of pointers

Lesson

[edit | edit source]

Pass by value vs Pass by reference; why we need to use pointer ?

[edit | edit source]

In this example, we want the function add() to change the value of the variable number we passed to it, i.e change from 6 to 7. Function add() returns the newly changed variable number to indicate the changing variable's process:

#include <stdio.h>

int add (int number);

main () {
	int number = 6;
	add(number);
	printf("%d \n", number);//6
	printf("add(number): %d \n", add(number));//add(number): 7 
}

int add(int number){
	number = number + 1;
	return number;
}

From the result, we can see that variable number hasn't been changed, although the return of function add() states that the passed in variable number has been changed.

The operation above is called pass-by value. What is sent to the function add(number) is the value of variable number (6), not the variable number, the variable number in add() is just "the clone" of the "genuine variable" number. Due to the value being copied, any changes made to the variable within the function are not permanent, they end with the function return. So the value of number remains unchanged.

For function add() to be able to change the value of the parameter, i.e number in this case, to it, we need a type of variable which is able to hold the "memory location" of that parameter so that function add() can perform the change directly in that memory location in order to "take effect" on that parameter. That's how we come to the definition of reference and pointer.

In both C and C++, a reference is defined as a value to stores the address of an object, e.g a variable, function, class object,... that is located elsewhere in memory.[1] Pointers are the most primitive type of reference.

The parameter to a function can be a copy of a value that is represented by a variable, which is known as pass-by value, or can be a reference to a memory space that stores value of variable, which is known as pass-by reference.

What is pointer ?

[edit | edit source]

A pointer is a variable holding a specific memory location. Most often, this memory location is that of a variable or array and it can also be the location of a function.

Pointers refer to memory addresses, as opposed to specific values. They are denoted by the use of an asterisk (*). Consider the following piece of code:

int i = 1;
int *k = &i;
int m = i;
printf("i (%p): %d\n", &i, i);
printf("k (%p): %d\n", k, *k);
printf("m (%p): %d\n\n", &m, m);
i++;
printf("i (%p): %d\n", &i, i);
printf("k (%p): %d\n", k, *k);
printf("m (%p): %d\n", &m, m);

/* OUTPUT

i (0x7fff62b5fa54): 1
k (0x7fff62b5fa54): 1
m (0x7fff62b5fa50): 1

i (0x7fff62b5fa54): 2
k (0x7fff62b5fa54): 2
m (0x7fff62b5fa50): 1

*/

In this code, k is set to the address of i, where m is set to the value of i. When i is incremented, the change is shown in k, since they both refer to the same memory address. m stays the same, however, since it took the value once, and stored it in a separate memory location.

In the brackets can also be seen the memory address of the variable, which shows that i and k refer to the same memory address, while m refers to a different memory address. Note that in the example, the following are true:

  • We used int * to declare a pointer to an integer
  • We used &i to get the memory address of i
  • We used *k to get the value stored in the address referred to by the pointer k. This is known as dereferencing pointer

Back to the introduction example, when we want a function to be able to change the value of a variable as parameter passed to this function. With the knowledge of pointers and pass-by reference, we have the solutions for that:

Using pass-by reference

#include <stdio.h>

int add (int *number);

main () {
 int number = 6;
 add(&number);
 printf("%d", number);//7
}

int add(int *number){
	*number = *number + 1;
	return *number;
}

Assign pointer to the variable

#include <stdio.h>

int number = 9;
int *pointer = &number;

int add(int *number);

main () {
    add(pointer);
    printf("%d", number); //10
}

int add(int *number){
	*number = *number + 1;
	return *number;
}

Pointers as array iterators

[edit | edit source]

When pointers are learned, we can use then for manipulating arrays effectively. Note that due to alignment, an array will require a memory alignment equal to the sizeof() the type of the array's elements.

Pointers can be incremented, which makes them a natural choice for iterating an array. Consider the following piece of code:

int a[5] = {0,2,5,8,11};
int *ptr = &a[0];
for (int i = 0; i < 5; i++) {
        printf("%p: %d\n", ptr, *ptr);
        ptr++;
}

/* OUTPUT

0x7fff645ffa40: 0
0x7fff645ffa44: 2
0x7fff645ffa48: 5
0x7fff645ffa4c: 8
0x7fff645ffa50: 11

*/

ptr initially refers to the first item in the array, a[0]. When ptr is incremented, the memory address is incremented by sizeof(int). The value at the address referred to by ptr is therefore then the next value in the array, accessed by *ptr.

Accessing address (a + i * sizeof(int)) accesses the (i+1)th element of the array.

Also note that arrays are always allocated with offset going upwards in RAM locations, your stack doesn't need to grow upwards for this.

Must not set value at an arbitrary memory location

[edit | edit source]

Based on the pointer concept, a pointer stores the value of another variable so directly setting the value for its dereferencing is a useless act and results in Segmentation fault

// THIS IS A WRONG IMPLEMENTATION AND IS ONLY USED FOR EDUCATIONAL PURPOSE. MUST NOT PRACTICE THIS IN REAL-LIFE APPLICATIONS
int *p = 9;//Segmentation fault

Modern computer systems use virtual memory techniques so that physical register addresses can't be set arbitrarily.[2] The following example is to arbitrarily setting value at a specific register address is the wrong implementation and must not be done in real-life applications:

// THIS IS A WRONG IMPLEMENTATION AND IS ONLY USED FOR EDUCATIONAL PURPOSE. MUST NOT PRACTICE THIS IN REAL-LIFE APPLICATIONS

// The target of this example is to set the value at register address 6295624
#include <stdio.h>

int *ptr;

int main() 
{
	ptr = (int *)6295624; 
	*ptr = 12;//Segmentation fault
	return 0;
}

To work with the program above with an address like ``6295624``, the address ``6295624`` must be the GCC allowable address. User must find the allowable address first with ``&`` for a dummy variable. E.g:

int dummy_variable;
printf("%d", &pdummy_variable);//print out the address of dummy_variable first to find the allowable address

Function Pointers

[edit | edit source]

Before we start with function pointers, it's worth mentioning that functions do have address:

void displayString(){
	printf("Hello, World !\n");
}

int main(int argc, char *argv[]) {
	printf("%d\n", displayString);//0x7fed62b5fa54, displayString has same value with  &displayString
	printf("%d\n", &displayString);//0x7fed62b5fa54, &displayString has same value with displayString
}

In addition to other ways that pointers can be used, they can also reference a function. While this may not seem very useful at first, it can be used to either embed a function in to a struct that uses it exclusively, or to allow a function to return a function or use a function as input to another function.

This is a very complex sub-topic and can create a lot of bugs in your program if poorly implemented.

Assignments

[edit | edit source]

Linked Lists

[edit | edit source]

A linked list is a list of data, comprised of nodes which specify their data, and the address of the next node, or NULL to specify the last node. We will use our knowledge on pointer to build a linked list. Consider the following code:

#include <stdio.h>
#include <stdlib.h>

struct Node {
        int data;
        struct Node * next;
};

int main() {
        struct Node * head = malloc(sizeof(struct Node));
        head->data = 0;
        struct Node * curr = head;
        for (int i = 1; i < 5; i++) {
                struct Node * new = malloc(sizeof(struct Node));
                new->data = 5*i;
                curr->next = new;
                curr = curr->next;
        }
        curr = head;
        while (curr != NULL) {
                printf("%d\n", curr->data);
                curr = curr->next;
        }
        return 0;
}

In this code, we defined a struct for a node in the linked list. We initialize the first node, and then add some data, by creating a new node, setting it to be after the current node, and then moving the current node to the next place. We do a similar process to iterate through the list.

A linked list is a good visualisation of pointers, but is also an effective data type for Last in First Out structures, such as stacks. Adding to a linked list is simply a matter of creating a new node, and setting its next to be the head. Removing is equally simple, get the value, deallocate the memory, and set the second node to be the head.

Completion status: About halfway there. You may help to clarify and expand it.

Reference

[edit | edit source]
  1. "References (C++)". learn.microsoft.com. Retrieved 2024-01-07.
  2. "Why does setting a value at an arbitrary memory location not work?". stackoverflow. Retrieved 2024-01-07.