Loading [MathJax]/jax/output/CommonHTML/fonts/TeX/fontdata.js

2 C Features - Pointers and Arrays

2.1 Pointers

The unique and core feature of C programming language is the capability of using memory addresses for data operations. C uses pointers to represent and store addresses and to do address operations. A pointer is a special type variable which stores the address of a data object (i.e. variable instance).

2.1.1 Variable name, value, and memory location

Every variable in C has a name (identifier) in a source code program. A variable is allocated a memory block at compile time, which is mapped to a memory location of a computer system at runtime. A variable is assigned a value in a source program, the compiler generates instructions, which write the value to the memory location of the variable at runtime. In other words, when the compiler encounters the statement of variable declaration and assignment of a value, it will allocate a memory block to the variable with a relative address, and generate instructions to write the value to the memory block. The relative address is an offset from a certain position. The size of the allocated memory block of a variable is the size of the data type of the variable. The compiler also puts the variable name and the relative address of the memory block and size into a table for later use of the variable.

For example, when the compiler processes statement int x = 1890259661;, it sets aside 4 bytes of memory block with a relative address, and generates instructions to write value 1890259661 to the memory block. At runtime, when these instructions are executed, the memory block is instanced with absolute address, i.e., having exact memory location and address, the value is written to the memory location.

For the convenience of accessing data objects by their addresses, C provides an operator & (reference operator) to get the address of an variable instance. For example, &x represents the address of variable x. At runtime, the value of &x is the address of the first memory cell of the memory block of the instance of x. The notation &x is called the reference of x.

C also provides a unary operator * to get value stored at a given address. For example, expression *(&x) represents the value stored at the memory address of &x. We have seen these notations in pass-by-references of functions. Listing 1 shows an example of using & and * in programming.

Compile and run the above program, we have the output like the following. Note that the address value may be different by computers.

Value of x is 1890259661
Runtime memory address of x in Hex is 0065FE9C
Runtime memory address of x in decimal is 6684316
Value stored at address 6684316 is 1890259661

Figure 1 illustrates the memory address and value 1890259661 in the memory block.

Figure 1: Variable name, memory, and value.
Figure 1: Variable name, memory, and value.

From Listing 1 and its output we see that, x is an int variable, x is assigned value 1890259661. &x represents the address of x and has runtime address 6684316. *(&x) represents the value stored at the memory location of address 6684316, namely 1890259661. *(&x) represents the value of variable x.

2.1.2 Concepts of pointers

The memory address of a variable is an integer number representing the lowest address of the memory block of the variable at the runtime. For the convenience of accessing data values by their addresses, C introduces a new type of variables, called pointers, to store the addresses of variables, thus makes it possible to access and operate data objects by their memory addresses.

A pointer is a variable to hold the memory address of another variable.

The syntax of declaring a pointer is data_type *ptr_name;

Here, * tells the compiler that ptr_name is a pointer type variable and data_type specifies that it will store the address of data_type variable.

The syntax to assign an address to a pointer is data_type x; ptr_name = &x;

It is called that ptr_name references to x, or ptr_name points to x.

Notation *ptr_name is called dereferencing ptr_name. Here, unary operator * is called dereference operator. Dereferencing a pointer is an operation to get the value stored at the memory location pointed by the pointer.

The expression *ptr_name tells compiler to generate instructions to:

  1. get the address value stored in ptr_name, then
  2. get the value stored at the memory location at the address given in ptr_name.

This process is called dereferencing a pointer, or getting value pointed by a pointer. In programming, *ptr_name represents the value or data pointed by ptr_name.

Notation *ptr_name can also be used to assign/set/store a value to the memory location pointed by ptr_name as *ptr_name = value.

Now we see, pointers provide an alternative way to get/set (i.e., access) data through the addresses of data objects.

Example:

int x = 10;  // this declares x as an int variable and initialize it with value 10 
int *p;      // this declares p as an int type pointer 
p = &x;      // this assigns the address of x to p
int b = *p;  // this dereferences p, and assigns the value to variable b, b will have value 10
b = b + 10;  // this increases b's value by 10
*p = b;      // this save the value of b to the memory location pointed by p, i.e. x, now x will have value 20

Note:

  1. A pointer must point to a proper memory location before it can be dereferenced, or set a value to; otherwise it leads to a runtime error due to non-proper address. For example, the following code will cause a problem at runtime even though it can pass compiling.
int *p;   // declare p as an int type pointer 
*p = 20;  // this does not work properly as p does not hold any valid address yet.
  1. Dereferencing a pointer provides an alternative method to get and set values at memory locations. But it is less efficient than directly using variable name because dereferencing needs first to get the variable address from the pointer, while using variable name will directly get the value from the variable location.
  2. The size of pointers is same for all types of pointers. The type of pointer is to assure that a pointer stores the address of data object of its type so that it gets the data value when dereferencing. For example, statements float x = 10.0; int *p = &x; will fail the compiling.
  3. Values of pointers are not application data. They are runtime intermediate or auxiliary data of running a program, they are meaningful when running the program, and they may be different at different running and on different computers.

Listing 2 illustrates using pointer for getting and setting values. Compile and run this program, check the output to see the value of pointer ptr and the value of dereferencing *ptr.

The output is like the following. Note that the address values may be different when you run it.

Value of x is 1890259661
Runtime memory address of x is 6684316
Value of pointer ptr is 6684316
Size of pointer ptr is 4
Runtime memory address of pointer ptr is 6684312
Value of *ptr is 1890259661
Value of *ptr is 10
Value of x is 10

Figure 2 shows the variable x and pointer ptr in memory. Note that the binary number stored at address 6684312 is 00000000 01100101 11111110 10011100, it represents decimal number 6684316, i.e., the runtime address of x. It also indicates that the pointer type variable ptr has 4 bytes. We use the 32-bit system, the address size is 32 bits (4 bytes). The sizeof pointer will be 8 if 64-system is used.

Figure 2: Pointer and memory
Figure 2: Pointer and memory

2.1.3 Pointer operations

Pointers represent and store data object addresses. The address values are integers. Pointers support the following operations.


2.1.4 Special pointers

Stop and think

Does it make sense to have pointers of three asterisks?

2.1.5 Pointers of functions

Pointers can be used to point to functions. This gives the flexibility to use one function pointer to represent different functions, and further more, makes it possible to pass a function to another function by references.

Function type pointers

Example:

int max();
int (*p)();       // this declares a function type pointer p
p = max;          // this lets p point to function max, that is  
                  // p holds the starting address of function block of max
c = (*p)(a, b);   // this uses function pointer p to call the function

Example of using function type pointers.

Functions as parameters

The following programming example shows how to use function type pointers as function parameters.

2.1.6 Memory allocations in C

Knowing the concepts of pointers, we can look into the dynamic memory allocation for data storage.

Allocating memory to store data is a fundamental task in programming. In a simple word, a memory allocation is to assign a memory block to store a data value of a certain type. C supports three memory allocation methods: static memory allocation, automatic memory allocation, and dynamic memory allocation.

Static memory allocation refers the memory allocation done by declaration of global or static variables. At compile time, a global or static variable is allocated a memory block with a relative address to the data region. At runtime, the statically allocated memory blocks are instanced with absolute addresses in the data region. The size of the data region is determined by the sum of the sizes of statically allocated memory blocks at compile time, and the size won’t be changed at runtime.

Automatic memory allocation refers to the memory allocation done by function arguments and local variables. At compile time, a local or argument variable is assigned a memory block with a relative address to the function scope. At runtime, the memory block of an automatic allocation is put in stack region when the function is called, and automatically released after the function call is finished. By release we mean that the space of the memory block can be reused by follow-up function calls. The automatic release means that the memory release is managed by program execution mechanism, not by program statements.

Dynamic memory allocation refers to the memory allocation done by stdlib library function malloc(). The memory block of a dynamic allocation is located in the heap region, and it won’t be released automatically after the calling function finishes. So dynamically allocated memory blocks can be shared by different functions. If a dynamically allocated memory block is used anymore, it can be released by using the free() function. The releasing will return the memory block to the pool of open memory space in the heap region.

For example, statement int *p = (int*) malloc (sizeof(int)); allocates a memory block of sizeof(int) bytes in the heap region with address stored in pointer p. Pointer p is used to access the memory block for setting and getting data. Statement free(p); will release the memory block, so that the memory block can be reused by other malloc() calls.

Example:

int *p = (int*) malloc (sizeof(int));
*p = 3;
printf("%d", *p);

The following is a list of C stdlib functions for the dynamic memory operations.

Memory leaking

It is important to keep the address of a dynamically allocated memory block by a pointer. If the address is lost, then the memory block can not be accessed, released, and reused. This situation is called memory leaking.

Example:

int *p = (int*) malloc (sizeof(int));
*p = 3;
printf("%d", *p);
p = NULL; // this causes a memory leaking

The following code does not cause memory leaking.

int *p = (int*) malloc (sizeof(int));
*p = 3;
printf("%d", *p);
free(p);  // release the memory block
p = NULL; // this won't cause a memory leaking

2.1.7 Summary

You learned that pointers are variables to store address of other variables. Pointers provide an alternative method to access data objects. Pointer values are runtime intermediate auxiliary data of a running program, they are meaningful when the program is running.

Pointers make it possible to do address operations for data access and operations. Such address based operations bring lots of flexibility, power, and features to C programming.

Stop and think:

Make a list of advantages and disadvantages of pointers.


2.1.8 Exercises

Self-quiz

Take a moment to review what you have read above. When you feel ready, take this self-quiz which does not count towards your grade but will help you to gauge your learning. Answer the questions posed below.

Go back