1. Summary
  2. Files
  3. Support
  4. Report Spam
  5. Create account
  6. Log in

Pointers

From cpwiki

(Redirected from A pointer on pointers)
Jump to: navigation, search

Contents

Introduction

This guide will explain the concept of pointers in C and C++, which may be somewhat confusing to newbies. It will start by explaining the basics of how virtual memory works and how pointers work inside the virtual memory, then about arrays and pointers, about operators, multi-dimensional arrays and operator ordering and lastly a few examples on the use of pointers.

Virtual Memory

Before digging into pointers, it is a good idea to know about Virtual Memory, in which all pointers are based. This article will assume the reader has basic knowledge about Virtual Memory, Virtual Addresses and pages. For more information, see this.

Pointer Basics

Let's focus on the stack first. Usually, a lot of variables are placed on the stack. These variables contain data (as opposed to pointers, which we'll get to later). When passing arguments to a function, it's possible to do it in two ways: the first is to do it by value. When passing by value, a copy of the variable is created inside the function and is assigned the value passed as argument. The function can then use this copy as it sees fit.

void foo(int CopyOfMyInt)
{
	std::cout << "Value of MyInt: " << CopyOfMyInt << ", address of MyInt: " << &CopyOfMyInt << std::endl;
}

int main()
{
	int MyInt = 100;
	std::cout << "Value of MyInt: " << MyInt << ", address of MyInt: " << &MyInt << std::endl;
	foo(MyInt);
	return 0;
}

This code shows clearly that we have two different variables with the same value. The output could be:

Value of MyInt: 100, address of MyInt: 0013FF60
Value of MyInt: 100, address of MyInt: 0013FE8C

Note that the virtual address where the variables are stored are not guaranteed to be the same all the time.

However, the problem comes when the called function wants to modify a variable passed to it and the changes should be done to the original variable that was passed to the function (or should be visible in the function that is the caller). To accomplish this task, the second way of passing arguments must be used. Passing by pointer. What this involves is defining an argument as a pointer. A pointer is a simple variable that contains a virtual address. By passing the actual address of the variable, the function can later modify the original variable.

void foo(int* MyInt)
{
	std::cout << "Address of MyInt: " << &MyInt << std::endl;
	std::cout << "Value of MyInt (address of original MyInt): " << MyInt << std::endl;
	std::cout << "Value of original MyInt: " << *MyInt << std::endl;
}

int main()
{
	int MyInt = 100;
	std::cout << "Value of MyInt: " << MyInt << ", address of MyInt: " << &MyInt << std::endl;
	foo(&MyInt);
	return 0;
}

This example shows 3 things. First it shows that the MyInt pointer is indeed a variable, because we can take its address and print it. Secondly, it shows how the value stored inside the variable is the original address of MyInt inside main! And the third line inside foo shows that if we dereference the pointer, we can see the original value of MyInt inside main!

An example output might look like this:

Value of MyInt: 100, address of MyInt: 0013FF60 <--- The same address as the value of MyInt inside foo!
Address of MyInt: 0013FE8C
Value of MyInt (address of original MyInt): 0013FF60 <--- The same value as the address of MyInt inside main!
Value of original MyInt: 100

When passing an argument to a function that expects a pointer, the address of a variable must be passed. To get an address of a variable, the address of operator & is used. By default, C and C++ works in such a way that typing the variable name accesses its value. So to be able to pass an argument to a function that requires a pointer requires that the address of the variable is passed instead of its value. It's easy to forget that the address of a variable must be passed where a pointer is expected. However, thankfully the compiler will halt with an error in such case (some C compilers actually compile it anyway but throws a warning). Therefore it is very important to pass the address of a variable instead of its value since otherwise it would (most likely) point to a page that the process does not have read and write access to, resulting in an access violation and usually a crash. Since the operating system only applies protection to pages and not individual bytes inside it, it means that an error may or may not occur if the process tries to write or read to memory it has not allocated, which can cause a so called buffer overrun.

void foo(int* MyInt)
{
	std::cout << "Address of MyInt: " << &MyInt << std::endl;
	std::cout << "Value of MyInt (address of original MyInt): " << MyInt << std::endl;
	std::cout << "Value of original MyInt: " << *MyInt << std::endl;
}

int main()
{
	int MyInt = 100;
	std::cout << "Value of MyInt: " << MyInt << ", address of MyInt: " << &MyInt << std::endl;
	foo(MyInt);
	return 0;
}

This code is invalid because we do not pass the actual address of MyInt to foo, thus the result is a compile error.
Visual Studio reports:

Error 1 error C2664: 'foo' : cannot convert parameter 1 from 'int' to 'int *' g:\w00t\visual studio 2008\projects\test\test.cpp 15
Compiled as C, Visual Studio shows the warning: Warning 1 warning C4047: 'function' : 'int *' differs in levels of indirection from 'int' g:\w00t\visual studio 2008\projects\test\test.cpp 20

g++ reports:
w00t@linux:~/foo> g++ -Wall -pedantic -o foo foo.cpp
foo.cpp: In function ‘int main()’:
foo.cpp:14: error: invalid conversion from ‘int’ to ‘int*’

(TODO: More errors/warnings from other compilers?)

void foo(int* MyInt)
{
	printf("Address of MyInt: %p\n", (void*)&MyInt);
	printf("Value of MyInt (address of original MyInt): %p\n", (void*)MyInt);
	printf("Value of original MyInt: %i\n", *MyInt); /* Oops! Crash! */
}

int main()
{
	int MyInt = 100;
	printf("Value of MyInt: %i, address of MyInt %p", MyInt, (void*)&MyInt);
	foo(MyInt);
	return 0;
}

The following code shows how, when compiled as C, the program runs but crashes because the value of MyInt (100) is passed instead of its address, which results in an invalid address. The output might look like this:

Value of MyInt: 100, address of MyInt 0013FF60
Address of MyInt: 0013FE8C
Value of MyInt (address of original MyInt): 00000064

64 is the hex representation of 100.

C have some tricky functions that make use of pointers and can be a little problematic. These include printf, scanf, fscanf, sscanf, and more. A very common error is to pass an incorrect argument to these functions, especially scanf which needs a pointer. These functions have variable argument lists, which means the compiler cannot check if the correct arguments are passed. Caution is recommended is recommended when calling these functions. Often it's easy to know what type of argument they need. Seeing as scanf writes data into variables passed into it, it needs a pointer to them. While printf simply prints data, it does not need pointers because it doesn't need to write back any data. The best thing to do is, of course, to check the documentation for any function before using them.

int main()
{
	int MyInt;
	puts("Enter an integer: ");
	scanf("%i", &MyInt); /* This is right because scanf writes into the buffer, it requires a pointer */
	scanf("%i", MyInt); /* This will cause the application to crash */
	printf("The entered integer was: %i", MyInt); /* This is right because printf does not write anything and only reads, it doesn't need a pointer */
	printf("The entered integer was: %i", &MyInt); /* This is wrong because printf will instead of printing the value of MyInt, print its address */
	return 0;
}

Another common mistake is simply to define a pointer and use as buffer. All variables have undefined values when they are defined, meaning that they can contain any value. And since pointers contain addresses, they will most likely point to some random page in memory that the process has to access to. That would result in an access violation. Or they could actually point to a valid address used by some other data, thus resulting in that data being overwritten, causing data corruption. Or it could point to some page the process has access to, but has not allocated. A so called buffer overrun.

int main()
{
	int* MyInt;
	int* MyInt2 = malloc( sizeof(int) );
	printf("MyInt: %p, MyInt2: %p\n", (void*)MyInt, (void*)MyInt2);
	puts("Enter an integer: ");
	scanf("%i", MyInt);
	printf("The entered integer was: %i", *MyInt);
	free(MyInt2);
	return 0;
}

MyInt is a dangling pointer because it hasn't been defined. In 99% of the cases, it does not point to a page where the process has read and write access, and therefore, when the scanf function tries to write to the address in this pointer, an access violation would occur and the process terminated. There are also chances that the pointer is pointing to a valid page by chance, and in this case, it can overwrite existing data used inside the application! Note that MyInt2 is allocated using malloc and is therefore pointing to a valid page where the process can read and write. Therefore, if MyInt was substituted for MyInt2 in scanf and printf, the application would work fine.

One variant of the output that can be produced by the application might be:

MyInt: 004010B9, MyInt2: 00033300
Enter an integer:
10

C++ also has its own special pointer type called references. It is essentially a pointer but with a different syntax.

using std::cout;

void foo(int& MyInt)
{
	cout << "MyInt value: " << MyInt << ", address of MyInt: " << &MyInt << std::endl;
}

int main()
{
	int MyInt = 100;
	cout << "MyInt value: " << MyInt << ", address of MyInt: " << &MyInt << std::endl;
	foo(MyInt);
	return 0;
}

Possible output:

MyInt value: 100, address of MyInt: 0013FF60
MyInt value: 100, address of MyInt: 0013FF60

It's not necessary to understand or believe that references are pointers, but it's possible to draw a conclusion if one understands the virtual memory concept and if one takes a peek at the assembly generated.

Arrays and pointers

Arrays are a special exception to the address of rule which is why it has its own section. Seeing as arrays can be huge, by default, C and C++ passes the address where the element starts instead of passing the array by value. This is also a common error. When a function is expecting a pointer, the address of operator must not be used to pass the array, since it would give a pointer to pointer to array instead.

void foo(char* str)
{
    std::cout << str << std::endl;
}

int main()
{
    char HelloWorld[] = "Hello World!";
    foo(HelloWorld); // Correct
    foo(&HelloWorld); // Incorrect
}

There is also a big debate on whether arrays are pointers or not. Suffice to say that an array cannot be passed "as an array." Instead, the address where the array starts is passed to functions, thus a pointer is actually created. The array itself is never passed. This means that it's possible to do a sizeof the array in the function it was defined and get correct size, but it cannot be used once passed to a function because a pointer is passed. Since pointers merely contain an address, the compiler cannot know the size of the array passed. Instead, it will give the size of the pointer itself.

Arrays can also be passed using another syntax, which is pretty common:

void foo(char str[])
{
    std::cout << str << std::endl;
}

int main()
{
    char HelloWorld[] = "Hello World!";
    foo(HelloWorld); // Correct
}

Although the syntax suggests that str in foo is actually an array, it's not. It's simply a pointer to the start of the array. (TODO: Add citation from standard?)

Multidimensional arrays and pointers

Let's begin by examining how a 2D array is built. A 2D array is laid out by rows and columns; seeing as the compiler sets aside one contiguous block of memory for a multidimensional array, one can simulate it with a pointer, or use automatic storage, like so:

foo bar[20][40];

Like flat arrays, multidimensional arrays are passed as pointers to a type, but this could be hidden with syntactic sugar. The sugar is often applied if you're already using an fixed-size array, because it wont require a cast at some point. ;)


void nowarnings (int foo[][40])
{
   /** no warnings here! **/
}

int main (void)
{
   int bar[20][40];

   nowarnings(bar);

   return 0;
}

When passing a multidimensional array to a function, only the first dimension's size may be elided, because pointer arithmetic is used behind-the-scenes to access array elements, (see the example) and as such requires the programmer knows the size of subsequent dimensions.

Because the allotment for multidimensional arrays is contiguous it should be possible to access every element with some arithmetic. The formula for bi-dimensional array access is

nth dimension times M dimensions plus xth element

If you use a pointer to an array type, the compiler can generate the correct formula for finding any element by itself while you use the convenient syntax, e.g. table[n][x]. If the reader is under the impression that this makes knowing the math useless: a 2d array could also be passed as a pointer 'without' dimension information, especially since this practice avoids a cast and a warning at compile time. Since the array pointed to is contiguous, the proper way to access elements would be through the math.

The following C example demonstrates what we've learned so far.


#define BUFFERLENGTH    16UL

void foo (char elems[][BUFFERLENGTH]);
void bar (char (* elems)[BUFFERLENGTH]);
void quz (char * table);

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void)
{
    /** a 2d array allocated exactly like the compiler would set aside
    *** for automatic storage arrays is needed: 
    **/
    char * table = malloc(sizeof table[0] * 2 * BUFFERLENGTH);

    if (table != NULL) {
        table[0] = '\0';
        strncat(table, "sample buffer 1", BUFFERLENGTH - 1);
        
        table[1 * BUFFERLENGTH + 0] = '\0';       
        strncat(&table[1 * BUFFERLENGTH + 0], "sample buffer 2", BUFFERLENGTH - 1);
        
        /** the compiler will give a diagnostic if you simply pass 'table' as-is to a function 
        *** expecting a pointer to an array, but as you can see, a cast would be totally 
        *** legitimate. 
        **/
        foo( (char (*)[BUFFERLENGTH]) table);
        bar( (char (*)[BUFFERLENGTH]) table);
        quz(table);

        free(table);
        table = NULL;
    }

    return 0;
}

void foo (char strings[][BUFFERLENGTH])
{
    strings[1][0] = 'S';
    printf("in foo: \"%s\"\n", strings[0]);
    printf("in foo: \"%s\"\n", strings[1]);
}

void bar (char (*strings)[BUFFERLENGTH])
{
    strings[0][0] = 'S';
    printf("in bar: \"%s\"\n", strings[0]);
    printf("in bar: \"%s\"\n", strings[1]);
}

void quz (char * strings)
{
    strings[0 * BUFFERLENGTH + 7] = 'B';
    strings[1 * BUFFERLENGTH + 7] = 'B';
    printf("in quz: \"%s\"\n", &strings[0 * BUFFERLENGTH + 0]);
    printf("in quz: \"%s\"\n", &strings[1 * BUFFERLENGTH + 0]);
}


It may be confusing, but it's important to remember that despite the sugar, it is still just a pointer. Something that can only be done on arrays (with correct result, such as sizeof) cannot be done inside the function you've called because it is honestly a pointer at the time.

Pointers

Pointers may be reassigned to contain a new address depending on the const correctness applied to the type. Further, we just had a look on how arrays works in conjunction with pointers and how the array was just a pointer when passed. Using the index operator [] on arrays is a granted given. However, the arrays were just pointers in the functions and yet still the code compiles. This is due to the fact that arrays are themselves handled as pointers in many contexts. Using the index operator on pointers just calculates the offset from the starting address in the pointer according to the formula above.

Further, it's also possible to use other operators such as ++ or -- or += or -= which are all common with pointer operations. Arrays being just a big block of memory, where each element starts at a given offset from the beginning, it's possible to traverse and change arrays using pointers. Using the ++ operator increases the offset in a pointer by sizeof(*type). So if it's a char pointer, it increases by 1 byte and if it's an integer, it increases by 4 bytes. This can be used to travel between each element in an array, for example. Similarly, += or -= adds (or removes) n number of sizeof(*type) to the pointer address.


int i[] = { 3, 5, 7, 9, 11 };
int * p = i;
int * q = &i[4];
p++;
p--;
q -= 2;    // point q at middle.
p += 3;    // point p at middle

Note also that to access a pointed-to object's value(s) through a pointer, it must be dereferenced first. This is done using the * operator. Using the index operator [] also dereferences the pointer.


#include <iomanip>
#include <iostream>

int main()
{
   using namespace std;

   int i = 2;
   int * p = &i;
   int j( *p );

   cout << boolalpha << ( j++ == p[0]++ ) << "\n\n";
}

Pointer to pointers

There also exists some confusion on pointer to pointers. A pointer to pointer to char is basically char**. What good are they and what are they used for one might ask? We know that it's possible to pass variables by pointer. When we define the pointer, we define it as the type of the variable it's supposed to point to followed by a *, so if we wanted to pass a char variable, we would use char*. Similarly, if we want to pass a char pointer by pointer, then we would end up with the type of char* and the following *, so it becomes char**! In C, pointer to pointer (or more levels) are typically used to allocate dynamic multi-dimension arrays. A pointer to pointer works just like pointers themselves. Dereferencing a char** gives the value of the char* it points to. Similarly, dereferencing that char* gives the actual char value.

(TODO: Add example)

Operator order

When working with pointers, it can be a good idea to know a little about the order of operators. If we have a char pointer to a char array and wish to increase the value it points to by one, do we do this?

*myvar++;

Since we have two operators here, we need to know the order of which they're evaluated. This can do one of two things:

  • Increase the address of the pointer by one, then dereference or
  • Dereference the pointer, then increase the value of the contents by 1.

The answer to what it does is the first. The ++ operator is evaluated first. To get around this, we must put predecease on the the dereference operation. We do this with parentheses:

(*myvar)++;

Basically it tells the compiler that the pointer should be dereferenced first and the value should be incremented afterwards. The same applies to other operators. The dereference operator * usually has a low priority.

There's also another operator which is named ->. It's a shortcut for the "(*myvar)." syntax. Therefore the -> operator has higher priority than the dereference operator.

mystruct* struct = new mystruct;
**struct.mypointer = 5;
*struct->mypointer = 5;

The first will give a compile error because the "." operator is evaluated before the dereference operator and a pointer doesn't have any members. The second line compiles fine because the operator -> has higher priority than the dereference operator and it will therefore access mymember first before dereferencing that variable. To make the first line work, we must prioritize the dereferencing of struct:

*(*struct).mypointer = 5;

It's possible to dereference every level of pointer level:

char**** myarray;
****myarray;
***myarray;
**myarray;
*myarray;

Similarly, if we want to access some member, we can apply previous knowledge:

mystruct**** struct;
(****mystruct).mymember = 5;
// Or
mystruct** struct
(***mystruct)->mymember = 5;

We must also prioritize the dereferencing here because otherwise we would dereference to mystruct*** and try to access its members, which is obviously wrong!

Examples of pointer usage

Some good examples of where pointers are used are linked lists. Another example is sometimes in strings (char arrays) and other arrays.

Personal tools