Hardwired's Pointers - C Approach






2.60/5 (13 votes)
Feb 23, 2005
10 min read

48542
Introduction to pointers - If you have no idea what are they and how to use them, read this!
Introduction
The Pointer is probably a most useful and smart thing that came along with the C language. Those who don’t realize this has no idea of what coding means (I always have the definition: “to Code” = “programming in C or C++ language”). The Pointer is a very powerful tool and can be used in various ways. More about this you’ll find out in this material.
Premises
I will begin with the most common and known definition: A Pointer is a type-variable in which is stored the memory address of an object. This means that the value contained in this variable is an address. Now, don’t think of those commonly used hexadecimal values, like 0x440562 that you have seen in various open-sources.
Let’s say that the third value, 156, is the value of a variable Var
. So, we have:
int Var; Var = 156;
The value of Var
is stored at the address 102. Now, we’ll declare a pointer:
int *pVar;
The following step is to assign a “value” to this pointer. If we assume that we know the memory address where the value of Var
is stored then we can do:
pVar = 102; // this is a correct assignment only in case we know the address
This is not at all recommended, because in most of the cases you don’t know the address. A code line like the one above, will give you a message of “Segmentation fault”, if you compile it and run it under Unix-like systems. What you can do is:
pVar = &Var;
The translation of the symbol “&
” is “address of”. So, this means that pVar
= address of Var
. Now, in the pVar
variable we have the value 102. We can say now that “pVar
points to Var
”, which means that the address of Var
is contained in the pVar
variable. The next step is to access the value stored at the address pointed to by pVar
. We do this by using the “*
” symbol:
int NewVar; // another variable NewVar = *pVar;
Now, we have assigned the value contained at the address pointed to by pVar
to the NewVar
variable. So, the translation of “*
” is “value at the address”. The output of:
printf( "value of NewVar = %d", NewVar );
will be:
value of NewVar = 156
Also, you can assign a value to the Var
variable by using the pVar
pointer. That’s because the address in which Var
is stored will not change and the pVar
is pointing at that address:
*pVar = 173;
So, “the value at address” pVar
is now 173 and the memory will look like this:
Types of pointers
There are different types of pointers. As I said before, a pointer is a variable that contains a memory address. But the value stored at that memory address can be of a specific type, like double
, int
or char
. That’s why there are different types of pointers.
int SomeVar; // a variable of int type int *pSV; // a pointer pSV = &SomeVar;
In the example above, we have assigned to pSV
the address of SomeVar
. To be able to do this (and not to get a nasty error message or warning – depending on the compiler), we have to declare: int *pSV
, which means that pSV
will point to a value of type int
.
Never try to do something like:
float AnotherVar; // avariable long *pAV; // a pointer pAV = &AnotherVar; // !!! no way !!!
This means that in pAV
you are about to store the address of a float
variable. But pAV
type is long
, and the ANSI standard tells us that something like this is not possible.
Operations with pointers
There are two categories of operations:
- Pointer operations.
- Operations with the values pointed by pointers.
The last category is already known, because those are same like the ones with normal variables:
double AVar; // first variable double BVar; // second variable double *pA; // first pointer double *pB; // second pointer AVar = 20; // we assign a value to first variable BVar = 12; // we assign a value to the second variable pA = &AVar; // make pA to point to AVar address pB = &BVar; // same for pB // // now we'll do several operations over the values of AVar and BVar: // (*pA)++; (*pB)--; (*pA) -= (*pB); printf( "value of AVar = %lf\n", *pA ); printf( "value of BVar = %lf\n", *pB );
The output will be:
value of AVar = 10
value of BVar = 11
Now, let’s see what operation we can do with the pointers, or better said, with the addresses stored in the pointers. But, before we start, we have to define the “address” word. To define it, we need to know the representation of memory and how the operating system works with it.
The memory can be “seen” as an array of bytes (1 byte = 8 bits). That’s for almost all of the computers, and this representation is related to physical, economical restrictions and practical reasons. The dimension of this array is equal to the amount of physical memory your computer may have. This doesn’t mean only the RAM that you install on it, but there is some extra memory to add here, like video ones and swap. So, the memory is an array of bytes and its elements can be accessed specifying an index. This index is called address. And here comes the first restriction:
You cannot assign to a pointer a negative value (look at the value as an address, not the value contained at that address). That's because the memory starts at 0 and ends at whatever your computer may have.
But this one is not the only restriction. Because the memory is being used, also by other processes (applications or OS managing operations), you cannot assign random values to the pointer, because you’ll probably crush the system (MS-DOS, Win 95, ’98, ME) or your application will get crushed (Unix-like systems and WinNT-based).
int *pC; // some pointer pC = 15; // assign some address (*pC) = 200; // !!! now, you've done it !!!
So, it’s better to let the system to manage the memory. You just work with it.
Now, we can get back to what this topic is all about: operations with pointers. There are not much: four of them: “+
”, “++
”, “-
”, “--
”. You can add two pointers, increment a pointer, subtract a pointer from another one, decrement a pointer. For a better understanding, let’s take an example:
float A; float B; float C; float *pA; float *pB; float *pC; A = 100; // assign some value B = 150; // same thing C = 14; // blah blah pA = &A; // you already know what this does pB = &B; pC = &C; pC = pA + pB; // add two pointers (1) pC++; // increment pointer(2) pA = pA - pB; // subtract(3) pB--; // decrement(4)
- Here we add two pointers,
pA
andpB
, and the result goes topC
. This means that we are adding the address contained by the pointerpA
with the address contained inpB
, and the result is stored inpC
. This is a pretty dangerous thing to do, if you don’t know anything about the resulting address. This is just for example and you have to understand that an operation like this one is allowed, but you have to know how to use it in safe conditions. - We can increment a pointer. The following explanations apply to the 4th case, too. When we’re incrementing an address, we have to be aware of the pointer’s type. In the case of a
char
type pointer – achar
variable is stored in 1 byte. So, achar
pointer will point to achar
value. If we are incrementing the pointer, then it’ll point to the nextchar
value. This means that if the address contained in the pointer is, let’s say, 100; and we’re incrementing it, the new value will be 101. This is not the same thing for along
orfloat
type pointer. For most operating systems (Unix-like and Windows suite), variables oflong
andfloat
type are stored using 4 bytes. Now, let’s say we have afloat
pointer = 100. If we’re incrementing it then the new value will be 104. It is very important for you to understand this, before you start using pointers in your applications. Also, when you use pointers, maybe you’d like to know what is the amount of bytes used to store a variable of a specific type. You can find out this with thesizeof()
function. - In this case, the rules are the same like for case 1. In addition, you’ll have to be careful no to get a negative value for the address. This means that the value of the pointer from which we are subtracting is not to be smaller than the value of the pointer subtracted.
- Like I have said before, the rules from case 2 apply in this case, too. Additionally, we have to check out if the pointer will be negative after decrementing it. If so, the operation is not allowed. Also, the address is an integer value and you cannot make operations like adding to or subtracting from a pointer a non-integer value, like in the following example:
double *pD; // a pointer pD = pD - 100.32; // !!!wrong!!!
Be aware that the pointer is
double
. That doesn’t mean that the address isdouble
. This means the value contained at that address isdouble
.
Independent pointers
That’s not a correct term, but I have considered it appropriate to be used in this case. In all the above topics, I explained only the cases in which we are assigning to the pointer the address of an already defined variable. But what can we do with pointers when we do not assign any address? How can they be used?
As I said before a pointer value is an address. That address can be an existing one, like the address of an already defined variable. You, also, allocate memory to it and work with the pointer. This means that you’ll not have to assign an address to it anymore… but allocate to it an address of its own. This way you’ll work with the pointer as it is and not define any unnecessary variable:
int *pVar; // // allocate momory for the pointer // why? because this pointer does not POINT to an already existing // data, so, we at least could kind enough to give it some memory // to work with:) // pVar = (int*)malloc( sizeof(int) ); *pVar = 56;
By saying “allocating memory”, you have to understand that the system will make some space, somewhere in the memory, for the pointer, and will automatically assign the address of that location to the pointer. The malloc()
function makes all this. If some problem occurs during this operation then the address assigned to the pointer will be NULL
, which means no address. You can check this using the following method:
pVar = (int*)malloc( sizeof(int) ); if( NULL == pVar ) // !!!this is not required, but highly recommended!!! return <some error>; // if yes, return or do something about it, // anyway, DO NOT work with the pointer!!! *pVar = 56;
… and with this, you are prepared for the next topic:
Pointers to arrays
In C language, there is an enclose relation between pointer and array, these two entities being almost interchangeable. This relation is probably a most powerful and unique tool. When you use an array without index, you are in fact generating a pointer to the first element in the array, so the compiler will actually take everything till it encounters the first NULL
:
char szMessage[] = "this manual is great!"; //:) printf( "%s\n", message );
In the example above, I have passed to printf()
an array of char
s. The function takes the address of the first element, this by converting it to a char
pointer. That’s because in C language it is impossible to pass to a function an array. You are only passing the address of the first element of it. Even if you weren’t aware of it, you already have used pointers. Because the name of an array without an index represents the address of the first element, you can assign this address to a pointer and you’ll have access through it, to any element in the array. These kind of pointers are named arithmetic pointers.
int Array[ 6 ] = { 1, 2, 3, 12, 54, 5 }; int *pArray; int i; // assign to the pointer the address // of the array's first element pArray = Array; // // the following code is the classic method // of accessing the elements of the array: // for( i = 0; i < 6; i++ ) printf( "%d\n", Array[ i ] ); // // now, use the arithmetic pointer: // for( i = 0; i < 6; i++ ) printf( "%d\n", *(pArray + i) );
Pointers to structures
A commonly used C technique is to access a structure via a pointer. Declaring a pointer to a structure is done in the same way as for any other data-type described above.
struct SomeStruct { int AVar; // a int member of the structure double BVar; // a double member } AStruct, *pAStruct;
AStruct
is a variable of type struct SomeStruct
and pAStruct
is a pointer to a structure of this type. So, now we are allowed to assign to pAStruct
the address of the variable AStruct
.
pAStruct = &AStruct;
To access the members of the structure using the pointer, we will have to use the arrow operator “->
”.
pAStruct->AVar = 12;
This means that we have assigned the value “12” to the AVar
member of the structure AStruct
, using the pAStruct
pointer. Like the use of all the other kind of pointers I have described above, this one is very important, too. Let’s say you’re passing a structure, as a parameter, to a function. What the C compiler does is this: transfers the entire structure to the function that is being called. When we have very large structures, like the ones you can see in the WinAPI, there can be a very large loss of memory and processing time. Here comes the use of pointers to structures. If you will pass that structure to the function by using a pointer to it, the compiler will then only transfer the address of the structure. In very complex applications, this kind of techniques are vital.
Conclusions
A wise man has once said that “we are jumping to conclusions when we are bored of thinking”. The C language has become one of the most powerful non-Object-Oriented languages just because of the pointers. They offer you low-level access to the memory resources and a lot of speed in complex applications. I hope this material made you understand the power of pointers. If you will use them correctly at least once, I am sure that you’ll see why I am such a big fan of them.
Points of Interest
Well... if you had no idea what a pointer is, then, hopefully, you've learned from this material.
History
- Version 1.0 - 23 February 2005 - This material was posted three years ago on a different site. But that community does not have too much activity, so I don't like them anymore :).