Introducing C++ Part 3 (More on pointers)

In this article “a C program” means a program written in C or C++.

Any area of knowledge can be understood at varying degrees, ranging from a cursory overview to an in-depth, intuitive understanding. That higher level of understanding for C can only be achieved with a solid understanding of pointers and the management of memory.

The key to comprehending pointers is understanding how memory is managed in a C program. After all, pointers contain addresses in memory. If we don’t understand how memory is organized and managed, it is difficult to understand how pointers work.

When a C program is compiled, it works with three types of memory:

Static/Global

Statically declared variables are allocated to this type of memory. Global variables also use this region of memory. They are allocated when the program starts and remain in existence until the program terminates. While all functions have access to global variables, the scope of static variables is restricted to their defining function.

Automatic

These variables are declared within a function and are created when a function is called. Their scope is restricted to the function, and their lifetime is limited to the time the function is executing.

Dynamic

Memory is allocated from the heap and can be released as necessary. A pointer references the allocated memory. The scope is limited to the pointer or pointers that reference the memory. It exists until it is released.

SCOPE AND LIFETIME:
SCOPE                                  LIFETIME

Global: 
The entire file                        While Application Runs

Static: 
The function it is declared within     While Application Runs

Automatic (local): 
The function it is declared within     While Function Executes

Dynamic: 
Determined by the pointers that        Until Memory is Freed
reference this memory 
(reference counted)             

Understanding these types of memory will enable you to better understand how pointers work. Most pointers are used to manipulate data in memory.

Understanding how memory is partitioned and organized will clarify how pointers manipulate memory.

A pointer variable contains the address in memory of another variable, object, or function. An object is considered to be memory allocated using one of the memory allocation functions, such as the malloc function.

A pointer is normally declared to be of a specific type depending on what it points to, such as a pointer to a char. The object may be any C data type such as integer, character, string, or structure. However, nothing inherent in a pointer indicates what type of data the pointer is referencing. A pointer only contains an address.

Faster and more efficient code can be written because pointers are closer to the hardware. That is, the compiler can more easily translate the operation into machine code. There is not as much overhead associated with pointers as might be present with other operators.

Many data structures are more easily implemented using pointers. For example, a linked list could be supported using either arrays or pointers. However, pointers are easier to use and map directly to a next or previous link. An array implementation requires array indexes that are not as intuitive or as flexible as pointers.

Dynamic memory allocation is effected in C through the use of pointers. The malloc and free functions are used to allocate and release dynamic memory, respectively. Dy‐ namic memory allocation enables variable-sized arrays and data structures, such as linked lists and queues. However, in the new C standard, C11, variable size arrays are supported.

Pointers represent a powerful tool to create and enhance applications. On the downside, many problems can occur when using pointers, such as:

• Accessing arrays and other data structures beyond their bounds

• Referencing automatic variables after they have gone out of existence

• Referencing heap allocated memory after it has been released

• Dereferencing a pointer before memory has been allocated to it
The NULL macro is a constant integer zero cast to a pointer to void. In many libraries, it is defined as follows:
#define NULL ((void *)0)

This is what we typically think of as a null pointer.

The actual internal representation of null is implementation-defined. The use of NULL and 0 are language-level symbols that represent a null pointer.

The ASCII NUL is defined as a byte containing all zeros. However, this is not the same as a null pointer.

A C-style string is represented as a sequence of characters terminated by a zero value.

The null string is an empty string and does not contain any characters.

Finally, the null statement consists of a statement with a single semicolon. As we will see, a null pointer is a very useful feature for many data structure implementations, such as a linked list where it is often used to mark the end of the list.

If the intent was to assign the null value to pi, we use the NULL type as follows:

 pi = NULL;

A null pointer and an uninitialized pointer are different. An uninitialized pointer can contain any value, whereas a pointer containing NULL does not reference any location in memory.

Interestingly, we can assign a zero to a pointer, but we cannot assign any other integer value.
A pointer can be used as the sole operand of a logical expression. For example, we can test to see whether the pointer is set to NULL using the following sequence:

if(pi) { // Not NULL, do Work; }

else { // Is NULL, exit;  }

A null pointer should never be dereferenced because it does not contain a valid address. When executed it will result in the program terminating.

NULL should not be used in contexts other than pointers. It might work some of the time, but it is not intended to be used this way.

It can definitely be a problem when used in place of the ASCII NUL character.

This character is not defined in any standard C header file.

It is equivalent to the character literal, ‘\0’, which evaluates to the decimal value zero.

Something to keep in mind (and keep sane):

int num;

int *pi = 0; // Zero refers to the null pointer, NULL

pi = #

*pi = 0; // Zero refers to the integer zero

We are accustomed to overloaded operators, such as the asterisk used to declare a pointer, to dereference a pointer, or to multiply.
The zero is also overloaded.

We may find this discomforting because we are not used to overloading operands.

Pointer to void

A pointer to void is a general-purpose pointer used to hold references to any data type.

An example of a pointer to void is shown below:

void *pv;

It has two interesting properties:

• A pointer to void will have the same representation and memory alignment as a pointer to char.

A pointer to void will never be equal to another pointer.

However, two void pointers assigned a NULL value will always be equal, because there are only one nullpointer which is shared throughout a program.

Any pointer can be assigned to a pointer to void.
It can then be cast back to its original pointer type.
When this happens the value will be equal to the original pointer value.
This is illustrated in the following sequence, where a pointer to int is assigned to a pointer to void and then back to a pointer to int:

int num;

int *pi = #

printf("Value of pi: %p\n", pi);

void* pv = pi;

pi = (int*) pv;

printf("Value of pi: %p\n", pi);

When this sequence is executed as shown below, the pointer address is the same:

Value of pi: 100

Value of pi: 100

Pointers to void are used for data pointers, not function pointers.

That’s it for now.

Until next time, Happy Hacking!
Advertisements

Now what's on your mind?

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s