Get up to 80 % extra points for free! More info:

Lesson 1 - Introduction to pointers in the C language

Welcome to the first lesson of an advanced course about programming in the C language. In this course, we'll learn how to work with dynamically allocated memory in the C language and get to work with files. Perhaps you won't be surprised that the prerequisite for the success in this course is the knowledge of the basic constructs of the C language.

Memory addresses

When we first mentioned variables, we said that variable is a "place in memory" where we can store some value. We also know that variables have different data types (such as int) and occupy as much memory as this type requires (for example, int takes 32 bits, i.e. 32 ones and zeros).

We can imagine the computer's memory as a long (almost endless :) ) sequence of ones and zeros. Some parts of the memory are occupied by other applications, and some are considered as free space by the operating system. In order to be able to work with the memory, it's addressed like houses in a street. The addresses are mostly specified in hexadecimal system but they are still ordinary numbers. The addresses goes chronologically following one another. Each address represents 1 byte of memory (i.e. 8 bits, since addressing single bits would be impractical).

Once we declare a variable in a C source code and run the application, C asks the operating system for the memory needed for this variable. The system then reserves this memory and returns its address to which the value of the variable can be stored (simply put). We call this process memory allocation.

Retrieving variable address

The C language has completely relieved us from working with addresses till now. It allocated memory for us, and we were working with our variables simply using their names. Now, let's create a simple program that declares a variable of the int type and stores a value of 56 in it. We're going to retrieve the address of this variable using the reference operator & (ampersand) and print it to the console. In the format string, we'll use %p which prints an address in the hexadecimal system as it's proper when it comes to memory addresses.

int main(int argc, char** argv) {
    int a;
    a = 56;
    printf("The a variable with the value of %d is stored in the memory at address %p", a, &a);
    return (EXIT_SUCCESS);
}

The result:

c_pointers
The a variable with the value of 56 is stored in the memory at address 0x23aadc

You can see that the system has chosen the address 0x23aadc on my computer. You'll have a different number there. The situation in the computer's memory will look like this:

Computer memory - Dynamic Memory Allocation in the C language

(The int data type has 32 bits, so it occupies 4 eights of bits on 4 addresses. We always specify the address where the value starts from.)

Pointers

Getting the address number is nice, but if we worked with memory that way, it would be rather impractical. That's why the C language supports pointers. Pointer is a variable whose value is an address to some place in the memory. However, C doesn't treat pointers as ordinary numbers, it knows it should use them as addresses. When we store something into a pointer or retrieve its value, it's not the address (the pointer's value) which is returned, but value that the pointer is pointing to is used.

Let's get back to our program again. This time, except for the variable, we'll define a pointer to the a variable as well. It'll also be of the int type, but we'll write the dereference operator * (asterisk) before it. It's a good habit to name pointers so they start with p_. Get used to it. It avoids major problems in the future since pointers are relatively dangerous, as we'll find out later. Why we should make it clear whether a variable is a pointer or not.

int main(int argc, char** argv) {
    int a, *p_a;
    a = 56;
    p_a = &a; // Stores the address of the a variable into p_a
    *p_a = 15; // Stores the value of 15 at the address of p_a
    printf("The pointer p_a has the value of %d and it points to the value of %d", p_a, *p_a);
    return (EXIT_SUCCESS);
}

The application creates an int variable and a pointer to int. Pointers also always have their data type depending on the type of the value they are pointing to. A value of 56 is stored in the variable a.

The address of the variable a is stored to the pointer p_a (with no asterisk for now). We retrieve the address using the reference operator &. Now we want to store the number 15 into where the pointer p_a points. Using the dereference operator (*), we don't store the value into the pointer but rather into where the pointer points to.

Then we print the value of the pointer (which is an address in the memory, usually a high number, here it's printed using the decimal system). And then we print the value that the pointer points to. Whenever we work with the value of a pointer (not it's address), we use the * operator.

The result:

c_pointers
The pointer p_a has the value of 23374500 and it points to the value of 15

Again, let's look at the situation in the memory.

Computer memory - Dynamic Memory Allocation in the C language

Passing by reference

Now, we can create a pointer to a variable. But what is it good for? We already could assign to a variable before. One of the advantages of pointers is passing by reference. Let's create a function that accepts 2 numbers as parameters. We'll want it to swap the numbers, assign the value of the first number to the second and vice versa. We could naively write the following code:

// This code doesn't work
void swap(int a, int b)
{
    int auxiliary = a;
    a = b;
    b = auxiliary;
}

int main(int argc, char** argv) {
    int number1 = 15;
    int number2 = 8;
    swap(number1, number2);
    printf("In number1 there's number %d and in number2 there's number %d.", number1, number2);
    return (EXIT_SUCCESS);
}

The result:

c_pointers
In number1 there's number 15 and in number2 there's number 8.

Why isn't the application working? When calling the swap() function in the main() function, the values of the number1 and number2 variables are taken and copied into the variables a and b in the function definition. The function further changes these a and b variables, but the original number1 and number2 variables remain unchanged. This approach, when the value of a variable is copied to a function parameter, is called passing by value.

Notice that we need an auxiliary variable in order to swap 2 numbers. If we only wrote a = b; b = a; in the swap() function, there would be the value of b in both variables, since the a value has been overwritten by the first command.

We can pass any variable by reference if we modify a function to accept pointers as parameters. To call such a function, we'll use the & reference operator:

void swap(int *p_a, int *p_b)
{
    int auxiliary = *p_a;
    *p_a = *p_b;
    *p_b = auxiliary;
}

int main(int argc, char** argv) {
    int number1 = 15;
    int number2 = 8;
    swap(&number1, &number2);
    printf("In number1 there's number %d and in number2 there's number %d.", number1, number2);
    return (EXIT_SUCCESS);
}

The result:

c_pointers
In number1 there's number 8 and in number2 there's number 15.

Since we now pass the address to the function, it's able to change the original variables.

Some C programmers often use function parameters to return values. However, this isn't very readable and if the performance is not a problem, and it's at least a little possible, a function should always return only one value using the return statement. It can eventually return a structure or a pointer to a structure/array.

Maybe it crossed your mind that you finally understand the scanf() function which stores values into variables passed through its parameters. We use the & operator here to pass the address at which the function should store the data at:

int a;
scanf("%d", &a);

Passing arrays

Arrays and pointers have a lot in common in the C language. Therefore, when we pass an array to a function parameter and change the array in the function, the changes will be reflected in the original array. Unlike other types, arrays are always passed by reference without us having to make any effort.

void fill_array(int array[], int length)
{
    int i;
    for (i = 0; i < length; i++)
    {
        array[i] = i + 1;
    }
}

int main(int argc, char** argv) {
    int numbers[10];
    fill_array(numbers, 10);
    printf("%d", numbers[5]); // Prints number 6
    return (EXIT_SUCCESS);
}

As we have said before, an array is actually a continuous space in memory. But we have to address such space somehow. We address it using pointers. Variables of the array type are nothing more than pointers. This means that the following assignment operation will be executed smoothly:

int array[10];
int* p_array = numbers;

In the Pointer arithmetics lesson, we're going to show that it doesn't matter whether we have an array or a pointer.

NULL

We can assign a NULL constant to all pointers of any type. It indicates that a pointer is empty and that it doesn't point to anything now. On most platforms, NULL equals to 0, so you may encounter assigning 0 instead of NULL in some codes. This is generally not recommended due to compatibility issues between different platforms. We'll use this value quite often in the future.

Remember: Pointer is a variable in which a memory address is stored. We can work either with this address or with the value at this address using the * operator. We get the address of any variable using the & operator.

Although we introduced pointers quite well, their real purpose is dynamic memory allocation. We'll look at it in the next lesson, Dynamic memory allocation in the C language.


 

All articles in this section
Dynamic Memory Allocation in the C language
Skip article
(not recommended)
Dynamic memory allocation in the C language
Article has been written for you by David Capka Hartinger
Avatar
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities