Pointers in C: Why Your Program Crashes

c pointersIn the C programming language, pointers are a powerful tool. If you use them correctly, they can improve the efficiency of the software – reducing how much memory is used, and increasing the how quickly the program runs. However, it is easy to make a mistake with pointers, so they are one of the most common causes of crashes. By learning how to use them properly, you can take advantage of their flexibility without causing problems.

If you are a new to programming in C, you would enjoy learning C programming from this course.

What Are Pointers?

Pointers are a kind of variable. But while most variables contain a simple value, a pointer contains the location of where a value can be found.

With older systems – particularly the popular 8-bit home computers of the 1980s – you can think of memory as a “table” of bytes. The location, or “address”, of a single byte is its position in this table.

Memory-Map

For example, when declaring a single byte variable (such as a “char”) with the value 25, the compiler might choose to place the number at the address $0003.

A pointer also occupies an entry in memory and contains a number. However, this number is the address of the value being referenced.

This is useful because the address in a pointer variable can be changed to refer to a different location, without copying or affecting existing data.

In modern computer systems, the arrangement of memory is more complicated than this. However, it can still be helpful to imagine blocks of memory as these kinds of tables.

Creating and Using Pointers

The sample code below declares an integer variable named “v1”. The next line creates a pointer to v1.

int v1 = 8;
int *v = &v1;

The declaration of a pointer consists of a data type, followed by an asterisk, and then the name to be used. When assigning an address value to the pointer, an ampersand is used in front of the target variable. This is the “address-of” operator. The assignment statement above reads “the address value of pointer v is equal to the address of the variable v1.”

If outputting the values to a console, for example:

printf("V1: %u \n", v1);
printf("V: %u \n", v);

The first line prints the number 8 as expected, but the second line shows a different number because v contains an address, not the number 8. You need to output the number that v points to. To do this, use the “dereferencing” asterisk in front of the pointer’s name:

printf(“V: %u \n”, *v);

Changing Pointers

Once declared, you can reassign a pointer to a different address at any time:

int v1 = 8;
int v2 = 23;
int *v = &v1;
printf("V: %u \n", *v);
v = &v2;
printf("V: %u \n", *v);

The first printf() statement outputs the value 8 from v1. The second, identical, statement outputs the value 23 because the pointer v now references the memory used by the variable v2.

You must remember to use the address-of operator when reassigning pointers to the address of a variable. The following statement actually says that “the address value of pointer v is equal to the value stored in v2.”

v = v2;

It changes the pointer v to reference the memory at address 23. If you then try to dereference, you are accessing memory that you did not intend to. In a modern environment, the operating system may reject this attempt and terminate your program unexpectedly.

Changing Values

To change the value stored at the memory address that a pointer references, you use the dereferencing asterisk:

*v = 7;

The code below assigns the pointer v to the memory used by the variable v1. It then changes the value stored at this address. The printf() statement shows that the value in v1 also changes when this happens.

int v1 = 8;
int *v = &v1;
*v = 7;
printf("V1: %u, V: %u\n", v1, *v);

There are often times when you will be forced to use pointers: for dynamic memory allocation, and when using functions from an operating system’s library. There are also times when you will choose to use them.

Pointers as Function Parameters

When you pass a variable into a function, a copy of the data is often created. This protects the original variable from being modified, but can also waste memory and CPU resources. At times, you will need your function to modify the original variable.

As with other kinds of variable, you can pass a pointer into a function. Doing this has a significant advantage over using “global variables”: your function can be moved into another project or library without creating the need for that global variable.

Declaring a function that accepts a pointer is not very different from declaring a function with a regular parameter:

void TestFunction(int *vx) {
*vx = 0;
}

You can then pass a pointer into this function and modify the value of v1 indirectly:

int v1 = 8;
int *v = &v1;
TestFunction(v);
printf("V1: %u \n", v1);

Function Pointers

Many programming languages have complicated ways of doing something that C can do easily: function pointers.

Pointers are not restricted to referencing variables, they can also point to an area of memory containing a function. For example, using the declaration of TestFunction() above, you can create a pointer to this function using a declaration and assignment statement:

void (*fp)(int*) = TestFunction;

This statement breaks down into four steps:

  1. State the return type of the function.
  2. Start the pointer’s name with an asterisk and enclose it in parenthesis.
  3. In another set of parenthesis, state the same parameters as used in the function definition, but without the parameter names.
  4. Complete the assignment statement with an equals sign and stating the name of the target function.

TestFunction() can now be called using the pointer:

fp(v);

You can reassign the pointer to a different function at runtime, provided that the new target has the same return type and parameters. So the call fp(v) can run different code depending on what happened earlier in the program’s execution.

You can store function pointers in structures, and even pass them as arguments into other functions.

Pointers and Arrays

The key to understanding how pointers work with arrays, is knowing that an array is already a kind of pointer.

The array declaration below declares udemy as a pointer to the first character in the string. Each subsequent character in the string is held in consecutive locations in memory.

char udemy[] = “UDEMY”;

As the following samples demonstrate, there are often no practical differences between arrays and pointers.

char udemy[] = "UDEMY";
char *upp = udemy;
printf("Array: %s \n", udemy);
printf("Pointer: %s \n", upp);

No address-of operator is used when assigning the value to upp. This still places the address of the string into upp because the value of udemy is an address, not a character or sequence of characters.

Since arrays are pointers, you can often treat pointers in the same way as you do arrays. The following statement is completely valid:

printf("Character 1: %c \n", upp[0]);

This means that, although appearing strange, the following is also perfectly acceptable:

short x = 32597;
char *p = (char*)&x;
printf("Byte 1: %u \n", *p);
printf("Byte 2: %u \n", p[1]);

A short is a 16-bit number, made up of two bytes, which fills up two consecutive locations in memory. Creating a char pointer to x, references the address of the first byte of the number. Using the array-addressing syntax with brackets is a simple way of accessing the second byte.

There is another way of doing the same thing. Since the address value of a pointer is a number, “pointer arithmetic” can move the pointer through memory.

The function call:

printf("Byte 2: %u \n", p[1]);

Can be written:

printf("Byte 2: %u \n", *(p+1));

Unary operators can also be used to move a pointer, as shown by this change to the sample code:

short x = 32597;
char *p = (char*)&x;
printf("Byte 1: %u \n", *p);
p++;
printf("Byte 2: %u \n", *p);

If you are not careful, you can move a pointer until it references memory outside the space that you are permitted to use. You must avoid doing this or the program may crash.

Pointers in Object-Oriented Programming

The C++ and Objective-C languages were developed as extensions of C. These were needed to make it possible for the language to work with the principles of object-oriented programming.

Both C++ and Objective-C make use of pointers to hold the locations of where objects are stored in memory, and support the other uses of pointers that are described in this article. If you are moving on to object-oriented programming through great courses like C++ From Beginner to Expert or Objective C for Beginners, you will find that they work the same way as you have already seen.