Hello,
Brief Tutorial Revision: All examples can be compiled successfully in your compiler. Also, each example is compatable using the -Wall, -pedantic, -ansi, and -std=c89 GCC flags. Also, a few paragraphs have been modified, and code examples improved. Previous tutorial, Pointers #2, has been combined in this revision.
Introduction
Pointers are facile, yet confusing. Thinking in the programming world isn’t easy, and comprehending everything read, intricate. Even though pointers are for the experienced, as they say, I’m here to guide programmers consisting of all levels to greaten their understanding of pointers, and why they are pertinent to your everyday programming.
Pointer Fundamentals
Let me start off by stating, a pointer is a variable that contains the address of another variable. Pointers and arrays are closely related, though pointers are sometimes the only way to express a computation. Some also say pointers lead to more compact and efficient code. If you view the following example [1.1], it may be confusing and you’ll see the incompleteness of it:
int main() {
/* local variables */
char c;
char ch[2];
char *pch;
c = 'a';
ch[0] = 'a'
*pch = ch[0];
/* end program */
return 0;
}
Example 1.1: An incomplete look at pointers
Pointers and addresses require allocation to store its data. Unlike the char and char[] data types, which use locally stacked memory, pointers pass though existing memory stacks.
The unary operator “& gives the memory address of an existing object. This assigns the address of one variable and “points to another. Remember, pointers use existing memory, so we can either send a freed block of memory to a pointer, or assign it another variables’ address. Let me lead this with an example:
#include <stdio.h>
int main() {
/* local variables */
int n = 3;
int *ptr;
/* point ptr to n */
ptr = &n;
/* memory address */
printf("%p\n", (void *)ptr);
/* data */
printf("%d\n", *ptr);
/* end program */
return 0;
}
Example 1.2: A closer look into pointers and addresses
If you have tried to compile this short example, you may see that it did not result in a crash. This is because we sent our memory address of the variable n to ptr. Here, ptr does not receive its own block of memory, yet it points to an existing variable, n. The same implementation applies in other situations, such as arrays:
int main() {
/* local variables */
int n[5];
int *ptr;
/* point ptr to n */
ptr = &n[0];
*ptr = 3;
/* end program */
return 0;
}
Example 1.3: Sending a memory address to a pointer
Or, you might want to consistently move the pointers position to correspond with all 5 slots of the array {0, 1, 2, 3, 4}. This is called pointer arithmetic, and is very possible to do with ease. An example:
#include <stdio.h>
int main() {
/* local variables */
int n[5] = {10,20,30,40,50};
int *ptr;
/* points to n[0]*/
ptr = &n[0];
printf("ptr now points to n[%d] whose value is %d\n", (int)(ptr - n), *ptr);
/* points to n[1] */
ptr++;
printf("ptr now points to n[%d] whose value is %d\n", (int)(ptr - n), *ptr);
/* this expression references n[2], but ptr remains the same */
*(ptr+1);
printf("ptr now points to n[%d] whose value is %d\n", (int)(ptr - n), *ptr);
/* this decrements the value of the content of the location pointed by ptr */
(*ptr)--;
printf("ptr now points to n[%d] whose value is %d\n", (int)(ptr - n), *ptr);
/* this increments the value of the content of the location pointed by ptr */
++*ptr;
printf("ptr now points to n[%d] whose value is %d\n", (int)(ptr - n), *ptr);
/* end program */
return 0;
}
Example 1.4: Pointer arithmetic
Pointer arithmetic may seem confusing, but it’s just like adding and subtracting. *ptr is the data of your pointer, while ptr is the location. The “++ is an operator that remedies the choice of “+= 1, or in other terms, “increments the variable by 1. To reference extended positions, the recommended arithmetic would be “*(ptr+x) as x represents your incrementing position.
Pointers are used in real day-to-day programs. For example, take the following question and convert it to C syntax accordingly:
There are 3 people in room A, and 5 people in room B. If the people in each room switched rooms, how many people would be in room A and room B?
Note: This seems extremely easy and would best serve if we used pointers in this situation. Let me explain. As logically explained above, we are to swap the people within Room A and Room B, and produce an answer. With our current knowledge of pointers, this can be done feasibly:
#include <stdio.h>
void reverse(int *x, int *y);
int main() {
/* Represent room A and B */
int A = 3, B = 5;
/* send local variable addresses to function */
reverse(&A, &B);
/* print result */
printf("Room A: %d\nRoom B: %d\n", A, B);
/* end program */
return 0;
}
void reverse(int *x, int *y) {
/* temporary variable */
int temp;
/* hold temporary variable that wont change */
temp = *x;
/* set x to y */
*x = *y;
/* set y to what x was temporarily before */
*y = temp;
}
Code 1.1: Reversing two numbers using pointer knowledge
As stated in code, void reverse(int *x, int *y) did a simple calculation and reversed A and B. The reason we used a temporary variable was to store the data of x. If we had set “*x = *y and “*y = *x it would have caused a problem no matter which we would call first. To break it down, we would literally state “1 [[b]x[/b]] = 2 [[b]y[/b]], then “2 [[b]y[/b]] = 2 [[b]x[/b]]. Keeping the previous state of *x was very crucial.
Simply, We sent our function void reverse(int *x, int *y) the memory addresses of A and B. That’s where the unary operator comes in handy. Send the addresses of A and B to *x and *y, swap out the two and we’re done. If we had not worked with pointers, we would have to create another set of variables, say, A0 and B0, then send A to B0, and B to A0 yet implementing a swap with unnecessary code congestion.
» Pointers in the char data type realm
We just discussed pointers from the integer perspective, and one-dimensional figures. As we may know, there are multiple dimensions in a programming environment. In which we must take the appropriate steps to accommodate all surrounding aspects.
The character array realm is different from the integer, as we must deal with multiple instances in one variable. They both dwindle down to the fact of digits, as the ASCII Table defines. Standard ASCII, and signed char’s represent 0 thru 127. With those numbers, the char reads them as letters, or in the ASCII environment. To explain a character array is in the same mindset:
int main() {
/* local variables */
int iCh;
char ch; /* signed */
char ca[3];
ch = 'a'; /* or 97 in ASCII */
iCh = 98; /* 98 is 'b' in ASCII */
ca[0] = ch; /* first index is 'a' */
ca[1] = (char)iCh; /* second index is 'b' [type cast the integer to char] */
ca[2] = 'c'; /* third index is 'c' */
ca[3] = '\0'; /* fourth ends here */
/* end program */
return 0;
}
Example 2.1: Character arrays in the integer environment
The example shown is still within the “one-dimensional environment. Character pointers also need a valid memory address to write to, like integers.
Pointing a “character pointer to a valid memory address isn’t as simple as seen before from the integer perspective. The following can be utilized, but is not recommended by most:
int main() {
/* local variables */
char text[6] = "Hello";
char *ptr;
/* not recommended */
ptr = &text[0];
/* end program */
return 0;
}
Example 2.2: Pointing a pointer to, though not recommended
As seen in this example, we initialized text to “Hello which is 5 letters long resulting {‘H’, ‘e’, ‘l’, ‘l’ ‘o’, ‘\0’} 4 indices in the array system. Arrays always start at 0 and ‘\0’ is at the fifth location, [5].
Next, is a more efficient way of receiving the memory address of a local variable. Even though it’s not recommended, it ensures linkage between the two variables:
void pointTo(char **src, char *dst);
int main() {
/* local variables */
char text[3];
char *ptr;
/* point our pointer to our local variable */
pointTo(&ptr, text);
/* modify pointer data; affects local data due to pointer linkage */
ptr[0] = 'A';
ptr[1] = '\0';
/* end program */
return 0;
}
void pointTo(char **src, char *dst) {
*src = dst;
}
Example 2.3: Linking a pointer to a local variable
Not exactly a two-dimensional pointer. We just need to send a pointer’s address to a function resulting in the pointer’s “* pointer “*. In void pointTo(char **, char *); we point dst to the pointed pointer *src.
To ensure a pointer has a memory block without risking the chance of our local variable failing, we would take an opposite approach and allocate memory right from your machine's core.
» Memory allocation in C
We previously learned how to link memory addresses to pointers, but now its time to move to a more stable and serious approach: Handling memory from the core.
This is no time at all to write a function or algorithm to space partition memory and issue it accordingly. That’s why the ANSI standard incorporated a function within the Standard C Library called void *malloc(unsigned int);. This function works in a unique way, sending the pointer memory of an empty/unused memory blcok of your requested size. This function will fail if there is insufficient memory from the system. Here is an example:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main() {
/* local variables */
char *ptr;
/* dynamically allocate memory */
ptr = malloc(6);
/* if allocation failed */
if (!ptr)
/* end application */
return 0;
/* copy data to pointer */
strcpy(ptr, "Hello");
/* print data */
printf("%s\n", ptr);
/* free data */
free(ptr);
/* end program */
return 0;
}
Code 2.1: Allocating memory the right way
Let’s ask for 6 bytes of memory to work with. If it can’t be found, end the program before a fatal crash occurs. Else, copy 5 letters to our 5 bytes leaving enough room for the NULL terminator ‘\0’. Once we are finished with out memory, we will free it. void free(void *); deallocates any dynamically allocated memory if previously allocated by a call to malloc(). This function is also provided by the standard library stdlib.h. char *strcpy(char *, const char *); is also within the included function(s) written in the standard header files, though this one is declared in string.h. This function, strcpy(), isn’t hard to re-implement. In fact, it just takes a few pointer skills:
char* strcpy(char *dest, const char *src) {
char *s = dest;
while (*src)
*s++ = *src++;
*s = 0;
return dest;
}
Example 2.4: Copy one string to another
Note: Remember, if you have <string.h> included, don’t add these functions to our code example. Library’s and functions will conflict, yet leading to errors.
I won’t get into great detail on this function. The main functioning of this is, that we increment *src and *s while copying the data from *src to *s while *src still exists. Once done, set the null terminator, and return dest as *s points to it.
» Multi-dimensional pointers
As tricky as it may sound, multi-dimensional pointers are just as easy as one-dimensional pointers, so don’t give up!
As opposed to pointers, arrays can also work in a multi-dimensional realm. For example:
Lets take:
int myValue[2][3];
Or for a more detailed look would be as the following:
Rows/Columns Column 0 Column 1 Column 2
Row 0 myValue[0][0] myValue[0][1] myValue[0][2]
Row 1 myValue[1][0] myValue[1][1] myValue[1][2]
That should make a lot of sense. If its to confusing, lets break it down:
int myValue[2][3] = { {5, -3, 0}, {10, 17, -25} };
Rows/Columns Column 0 Column 1 Column 2
Row 0 5 -3 0
Row 1 10 17 -25
This is beginning to look simpler by the minute. As we previously discussed, the character environment is somewhat different, but not always.
Look at this example:
char letters[2][3] = { {'A'}, {'D', 'E'} };
Rows/Columns Column 0 Column 1 Column 2
Row 0 A ‘\0’ ‘\0’
Row 1 D E ‘\0’
As seen above, two-dimensional arrays consist of “row and “column, as one-dimensional consists of “column. Allocating memory to two-dimensional arrays can be tricky, so here are some good steps to remember:
- Know your Row count first.
- Know your Column count next.
- Know your Column length.
In this order, everything should come together smoothly. Lets take a look at an example:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
/* Local variables */
int i, j;
int rows = 6;
char word[6][12] = {"Hello", "Good-Bye!", "Greetings",
"Pointers", "Arrays", "Programming"};
char **ptr = NULL;
/* Allocate room for rows */
ptr = malloc(rows * sizeof *ptr);
/* If allocation succeeded */
if (ptr) {
/* Loop through all columns */
for (i = 0; i < rows; i++) {
/* Allocate */
ptr[i] = malloc(strlen(word[i]) + 1);
/* If allocation succeeded */
if (ptr[i])
/* Copy data to our pointers memory */
strcpy(ptr[i], word[i]);
}
}else {
/* Allocation failed */
return 0;
}
/* Print data */
for (i = 0; i < rows; i++)
printf("%s\n", ptr[i]);
/* Free memory */
for (j = 0; j < rows; j++)
free(ptr[j]);
free(ptr);
/* End program */
return 0;
}
Code 3.1: Using two-dimensional arrays
The comments in the code help guide you through each step. The sizeof function comes in handy during this process. We need to multiply our allocation size with the size of our variable to assure we have enough space to allocate anything else. Without that, our program may terminate unexpectedly.
Pointers may seem difficult at this point, but its making perfect sense. There comes a time when it begins to advance. I do hope this short tutorial has been helpful to you. Before I’m done, I’d like to explore three-dimensional pointers. This too has a background of difficulty, but in the right perspective it too can be made easy.
» Three-Dimensional Pointers
This is an easy subject I like to think. It’s as easy as 1, 2, 3. Think of this as a Book. A book is three dimensions; depth, row, and column.
For example:
int Book[2][3][4];
Book – Page[0]:
Rows/Columns Column 0 Column 1 Column 2 Column 3
Row 0 Book[0][0][0] Book[0][0][1] Book[0][0][2] Book[0][0][3]
Row 1 Book[0][1][0] Book[0][1][1] Book[0][1][2] Book[0][1][3]
Row 2 Book[0][2][0] Book[0][2][1] Book[0][2][2] Book[0][2][3]
Book – Page[1]:
Rows/Columns Column 0 Column 1 Column 2 Column 3
Row 0 Book[1][0][0] Book[1][0][1] Book[1][0][2] Book[1][0][3]
Row 1 Book[1][1][0] Book[1][1][1] Book[1][1][2] Book[1][1][3]
Row 2 Book[1][2][0] Book[1][2][1] Book[1][2][2] Book[1][2][3]
Or, in initialization:
int Book[2][3][4] = {
{ {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} } /* Page 0 */
{ {12, 13, 14, 15}, {16, 17, 18, 19}, {20, 21, 22, 23} } /* Page 1 */
};
Book – Page[0]:
Rows/Columns Column 0 Column 1 Column 2 Column 3
Row 0 0 1 2 3
Row 1 4 5 6 7
Row 2 8 9 10 11
Book – Page[1]:
Rows/Columns Column 0 Column 1 Column 2 Column 3
Row 0 12 13 14 15
Row 1 16 17 18 19
Row 2 20 21 22 23
I hope this is all coming together. Of course taking special measures for memory allocation is always needed. I’ll show an example:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
int i, j, k, p; /* For loops */
int pNum; /* Page number */
int pGraphs; /* Paragraphs per page */
char paragraph[4][256]; /* Our paragraphs */
char ***Book = NULL; /* Our book */
/* Copy data to string */
strcpy(paragraph[0], "Pointers are awesome!");
strcpy(paragraph[1], "Now that I have learned 3-dimensional arrays,");
strcpy(paragraph[2], "I can use this knowledge");
strcpy(paragraph[3], "to write my own programs!");
/* Initialize loop variables */
j = p = 0;
pNum = 2; /* 2 pages */
pGraphs = 4; /* 2 paragraphs per page */
/* Allocate memory */
Book = malloc(pNum * sizeof *Book);
if (Book) {
for (i = 0; i < pNum; i++) {
/* Allocate memory */
Book[i] = malloc(pGraphs * sizeof *Book);
/* If allocation failed */
if (Book[i] == NULL) {
/* Free any memory that was previously used */
for (k = i; k >= 0; k--) {
if (Book[k] != NULL) {
free(Book[k]);
Book[k] = NULL;
}
}
/* Free any memory that was previously used from beginning */
if (Book != NULL) {
free(Book);
Book = NULL;
}
/* Terminate program */
return 0;
}
for (j = 0; j < pGraphs; j++) {
/* Allocate memory */
Book[i][j] = malloc(strlen(paragraph[j]) + 1);
/* If allocation failed */
if (Book[i][j] == NULL) {
/* Free any memory that was previously used */
for (k = j; k >= 0; k--) {
if (Book[i][k] != NULL) {
free(Book[i][k]);
Book[i][k] = NULL;
}
}
/* Free any memory that was previously used in previous loop */
for (k = i; k >= 0; k--) {
if (Book[k] != NULL) {
free(Book[k]);
Book[k] = NULL;
}
}
/* Free any memory that was previously used from beginning */
if (Book != NULL) {
free(Book);
Book = NULL;
}
/* Terminate program */
return 0;
}
/* Copy data to pointer */
strcpy(Book[i][j], paragraph[j]);
}
}
}else {
return 0;
}
/* Print data */
p = 0;
printf("My two page book\n");
for (i = 0; i < pNum; i++) {
printf("\nPage: %i\n", i);
for (j = p; j < (p + 2); j++) {
printf("%s\n", Book[i][j]);
}
p = j;
}
/* Free Memory */
for (i = 0; i < pNum; i++) {
for (j = 0; j < pGraphs; j++) {
free(Book[i][j]);
Book[i][j] = NULL;
}
free(Book[i]);
Book[i] = NULL;
}
free(Book);
Book = NULL;
/* End program */
return 0;
}
Code 4.1: Using three-dimensional pointers
I’ll let you decipher that one on your own. Always remember; think of this code, as a book, and it will make complete sense.
Pointers in application
You have learned most, if not all, of the basics pertaining pointers in the previous tutorial. Here, we’ll discuss the fact-finding situations of pointers and their importance in the programming world. This tutorial is to help you understand pointers in application, how to dissect them, and how to use them properly.
Pointer arithmetic is also a crucial segment of learning pointers in their entirety. I briefly covered this subject in the previous tutorial, which will indeed lead to a more successful approach in pointer utilization in the near future.
Fully understanding pointers will further help you when you run into a problem, or become confused in your current program or project. For our first approach, lets further explain the pointer usage:
#include <string.h>
int main() {
/* local variables */
char ch[25];
char *p;
/* point p to ch */
p = &ch[0];
/* copy data to pointer */
strcpy(p, "Test");
/* concatenate data */
memcpy(&p[4], "ing", 3);
p[7] = '\0';
/* exit program */
return 0;
}
Example 5.1: Utilizing pointers in a new view
That may seem weird, though all functions that retrieve a pointer point to where you send. Normally when you send a pointer, you don’t send its address. Since the function prototype asks for a single pointer, you send it the pointer. If we were to dissect the memcpy function, we may notice the first parameter looks different; at least while normal variable names were sent. Functions like memcpy() take a one-dimensional character array in one or more of their arguments.
We are aware that the & operator only applies to objects in memory. Also previously learned is that p points to ch; no matter which is changed, they both correspond. Also, we are aware of the fact that “p[4] is a single character from our array. In the cataclysmic event, we are complying with the one-dimensional array as to this extent: We are sending the object p at position 4. Any changes to this variable will proceed with and after position 4.
If we were to take a more in-depth look, this is how the compiler perceives this: The & operator takes a one-dimensional array and then deals with the memory address; causing a “char * to be perceived as a “char **. Likewise with our object “p. p[4] is a character. The & operator will then make our “char look like a “char *. Since we defined which location to start at “[4], our new “char * will start at position 4 when converted.
Another Example:
int myFunc(int *);
int main() {
/* local variables */
int i = 3;
int j = 2;
int *k;
/* point k to j */
k = &j;
myFunc(&i); /* i isn’t a pointer, so send variable address */
myFunc(k); /* k points to j, k is already a pointer; don’t send address */
/* end program */
return 0;
}
int myFunc(int *x) {
int z = *x;
return z;
}
Example 5.2: When to send a memory address
It is pertinent to know when and when not to send a pointer address to a function. For example, as we just saw above, consider the following:
#include <stdio.h>
#include <stdlib.h>
int main() {
/* local variables */
char ch[25];
char *p;
/* p points to ch */
p = &ch[0];
scanf("%s", &p); /* incorrect */
/* same reason why */
fgets(&p, 24, stdin); /* is incorrect */
/* don’t send pointer address to either since p is already a pointer. */
/* end program */
return 0;
}
Example 5.3: Pointers address information not always needed.
Same applies for non-pointers in this case. The character array/pointer already contains an address, and when sending to a function, don’t send the address of unless you send the location. Many don’t understand why it isn’t appropriate to send the address of a character array to scanf(). It is simple actually. The reason we don’t send it is because we want to write to its data, not its address. The reason we do send it when dealing with an integer, float, double, or other is because scanf() takes pointers to objects. If you want to store the result on a standard variable, it is imperative to precede it with the reference operator.
Another Example:
#include <stdio.h>
#include <stdlib.h>
int main() {
/* local variables */
char ch[25];
char *p;
/* p points to ch */
p = &ch[0];
scanf("%24s", p);
/* Or */
fgets(p, sizeof ch, stdin);
return 0;
}
Example 5.4: Send pointer address including where to point to
This way, scanf() can write to p, not the address. The address is good when you need to directly gain access to a variable, though here we don’t need to.
Integer variables see otherwise. When sending information to a function that needs access, it usually means it wants the address too, like scanf(). Remember, sending address information is completely different when dealing with an array or not. The difference is very obvious:
int main() {
/* local variables */
int i, j[2];
int *p, *q;
/* p points to i */
p = &i;
/* q points to j */
q = &j[0];
*p = 3; /* i = 3 */
*++(q) = 1; /* j[1] = 1 */
/* end program */
return 0;
}
Example 5.5: Integers, and the difference between array’s and non-array’s
There is a large difference between integers and character arrays. Expounding on this seems nagging, though it is what most programmers need to understand. The integer array is controlled by indices. My array[0] is a number, then my array[1] is another number. They are handled separately, and require more hand-holding. The character array is different, you have an array, and it controls the whole block. No need to set one array index at a time.
» Pointer Techniques
Pointers can be used in many programs, varying from the instance. You can use them to allocate memory, point to other variables, change data from a specified address, and so on and so forth. It really depends on where you use them, and what they are being used for. For example, if you wanted to send a two-dimensional array to a function and allocate it there; it would be somewhat impossible to send the 2-D pointer and memory address and allocate it there. There are ways around this though, for instance we would send our 2-D pointer to a function. Then we would create a temporary 2-D pointer inside our separate function and allocate it there. Tell our sent object to equal the temporary variable, and we are on our way.
Here is an example:
#include <stdlib.h>
void alloc2D(char ***, int, int);
int main() {
/* Local variables */
int i;
char **string = NULL;
alloc2D(&string, 3, 5);
/* If allocation failed */
if (string == NULL)
return 0;
/* Free memory */
for (i = 0; i < 3; i++)
free(string[i]);
free(string);
/* End program */
return 0;
}
void alloc2D(char ***str, int row, int column) {
/* Local variables */
int i;
char **temp = NULL;
/* Allocate temporary variable */
temp = malloc(row * sizeof *temp);
if (temp)
for (i = 0; i < row; i++)
temp[i] = malloc(column + 1);
/* Object of str now links to temp */
*str = temp;
}
Code 5.1: Allocating a 2-dimensional array by passing pointers.
Conclusion
In conclusion of this segment, there are many ways pointers can be utilized in programs. This tutorial is just here to provide information on pointers, and how they work. With the fundamentals, it should not be difficult to debug your own programs and figure out why they may be performing the way it is.
- Stack Overflow