Hello fellow programmers,
This is a tutorial about using pointers in the C environment. Please, don’t hit the back button; pointers aren’t as hard as you think. In fact without pointers, most of us would be lost in our latter programming years.
Even though pointers are for the experienced, as they say, I’m here to help programmers from consisting of all levels to help greater understand pointers, and why they are pertinent to your everyday programming needs.
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 usually lead to more compact and efficient code. If you view the following example [1.1], it may confuse you or if not, you’ll see the incompleteness of it:
int main() {
char c;
char ch[2];
char *pch;
c = ‘a’;
ch[0] = ‘a’
*pch = ch[0];
return 0;
}
Example 1.1: An incomplete look at pointers
Pointers and addresses need 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 an empty or 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() {
int n = 3;
int *ptr;
ptr = &n;
printf("%p\n", (void *)ptr); // memory address
printf("%d\n", *ptr); // data
return 0;
}
Example 1.2: A closer look into pointers and addresses
If you have tried to compile this piece of code, you may see that it did not result in a crash. As you might ask “why? this is because we sent our memory address of the variable n to ptr which is results to giving ptr its memory block. Here, ptr does not receive its own block of memory, yet it points to an existing variable n. The same implementation applies for multiple situations, as arrays:
int main() {
int n[5];
int *ptr;
ptr = &n[0];
*ptr = 3;
return 0;
}
Example 1.3: Sending a memory address to a pointer
As you are probably wondering how the same applies if you 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:
int main() {
int n[5];
int *ptr;
ptr = &n[0]; // points to n[0]
*(ptr+1); // points to n[1]
++*ptr; // points to n[2]
(*ptr)++; // points to n[3]
return 0;
}
Example 1.4: Pointer arithmetic
Pointer arithmetic may seem confusing, but it’s just like adding and subtracting. *ptr is the location of your pointer, not actually the value when working with arrays. As you may have already learned, “++ is an operator that remedies the choice of “+= 1, or in other terms, “increments the variable by 1. To move multiple positions at once, the recommended arithmetic would be “*(ptr+x) as x represents your incrementing position.
Pointers are used in real programs. For example, take the following question and convert it to the C syntax:
There are 3 people in room A, and 5 in room B. If they were reversed, how many people would be in room A, and room B?
Note: This seems extremely easy and it would be best served if we used pointers in this situation. Let me explain, here, as logically explained above, we are to swap A and 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() {
int A = 3, B = 5; // represent room A and B
reverse(&A, &B);
printf(“Room A: %d\nRoom B: %d\n, A, B);
return 0;
}
void reverse(int *x, int *y) {
int temp; // temporary variable
temp = *x; // hold temporary variable that wont change
*x = *y; // set x to y
*y = temp; // set y to what x was temporarily before
}
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 for us easily. The reason a temporary variable was needed, is because 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]]. Using a temporary variable should become clear since keeping the previous state of *x is very crucial at this point.
In order for this code to have worked so clearly, we would need to send 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 steps.
Pointers in the char data type realm
Above we discussed pointers in the realm of integers, and one-dimensional figures. As we may know, there are multiple dimensions in a programming environment, and 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 line:
int main() {
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
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. Pointers our pointers, so that rule applies always.
Pointing a character pointer to a valid memory address isn’t as easy as seen before in the integer arena. The following can be utilized, but is not recommended by most:
int main() {
char text[6] = “Hello;
char *ptr;
ptr = &text[0]; // not recommended
return 0;
}
Example 2.2: Pointing a pointer to, though not recommended
As seen above, we initialized text to “Hello which is 5 letters long resulting {‘H’, ‘e’, ‘l’, ‘l’ ‘o’, ‘\0’} 4 in the array world. Arrays always start at 0, and ‘\0’ is at location [5] in this case.
Next, I will display a more efficient way of receiving the memory address of a local variable. Even though it’s not recommended, it ensures more linkage between the two:
void pointTo(char **src, char *dst);
int main() {
char text[3];
char *ptr;
pointTo(&ptr, text);
ptr[0] = ‘A’;
ptr[1] = ‘\0’;
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 yet, 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. The first explanation is in C.
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 out how much is asked for or called. That’s why the ANSI standard incorporated a pre-made function for us called void *malloc(unsigned int);. This function works in a unique way, sending the pointer a memory address of empty/unused memory to the size you request. This function can fail if insufficient memory is provided. Here is an example of how this function is used in programming:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main() {
char *ptr;
ptr = (char *)malloc(6);
if (!ptr)
return 0;
strcpy(ptr, "Hello");
printf(ptr);
free(ptr);
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 program before a fatal crash, else, copy 5 letters to our 5 bytes leaving enough room for the NULL terminator ‘\0’. Let’s print our pointer to the screen, and last of all, and never forget, free the memory. void free(void *); works like malloc(), though it frees the memory of the memory address sent in the first and only argument. 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 3.1: 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 Column 3
Row 0 myValue[0][0] myValue[0][1] myValue[0][2] myValue[0][3]
Row 1 myValue[1][0] myValue[1][1] myValue[1][2] myValue[1][3]
Row 2 myValue[2][0] myValue[2][1] myValue[2][2] myValue[2][3]
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} };
Could be looked at as:
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() {
int i, j, p;
int rows = 6;
char word[6][12] = {"Hello", "Good-Bye!", "Greetings",
"Pointers", "Arrays", "Programming"};
char **ptr;
ptr = (char **)malloc(rows * (sizeof *ptr)); // allocate room for rows
// If allocation succeeded
if (ptr) {
// Loop through all columns
for (i = 0; i < rows; i++) {
// Allocate
ptr[i] = (char *)malloc(strlen(word[i]) + 1 * (sizeof **ptr));
// If allocation succeeded
if (ptr[i])
// Copy data to our pointers memory
strcpy(ptr[i], word[i]);
}
}
// 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);
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 tutorial has been helpful to you. Before I’m done, I’d like to explain and explore three-dimensional pointers. This too has a background of difficulty, but in the right perspective is as easy as everything else.
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];
Think of it as:
Book – Page[0]:
Rows/Columns Column 0 Column 1 Column 2
Row 0 Book[0][0][0] Book[0][0][1] Book[0][0][2]
Row 1 Book[0][1][0] Book[0][1][1] Book[0][1][2]
Book – Page[1]:
Rows/Columns Column 0 Column 1 Column 2
Row 0 Book[1][0][0] Book[1][0][1] Book[1][0][2]
Row 1 Book[1][1][0] Book[1][1][1] Book[1][1][2]
Or better yet:
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
};
Or in other terms it would look like:
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, p; // For loops
int pNum; // Page number
int pGraphs; // Paragraphs per page
char paragraph[4][256]; // Our paragraphs
char ***Book; // Our book
strcpy(paragraph[0], "Pointers are awesome! Now that I have learned 3-dimensional arrays, I can use this knowledge to write my own programs!");
strcpy(paragraph[1], "Through learning one-dimensional to multi-dimensional, I now know that pointers aren't as bad as they may seem.");
strcpy(paragraph[2], "It may be hard to write your own book typing all the text in a 3-dimensional array, but it works for an example!");
strcpy(paragraph[3], "Good luck with using 3-dimensional pointers, I hope this has helped you alot in understanding how they work.");
j = p = 0; // Initialize loop variables
pNum = 2; // 2 pages
pGraphs = 4; // 2 paragraphs per page
// Allocate memory
Book = (char ***)malloc(pNum * (sizeof *Book));
if (Book) {
for (i = 0; i < pNum; i++) {
Book[i] = (char **)malloc(pGraphs * (sizeof **Book));
if (Book[i]) {
for (j = 0; j < pGraphs; j++) {
Book[i][j] = (char *)malloc(strlen(paragraph[j]) + 1 * (sizeof ***Book));
if (Book[i][j])
strcpy(Book[i][j], paragraph[j]);
}
}
}
}
// 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]);
free(Book[i]);
}
free(Book);
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.
I hope this tutorial shown the simplicity of pointers. Feedback is greatly appreciated, and good luck with your future programming.
- Stack Overflow