The final step of the pointer storage program is to add data to the various buffers, or “sticks,” where ten integer values are stored. This step involves ugly double-pointer notation. If you can, please review last week’s Lesson to get up to speed on how storage is allocated and how these pointers are managed.
When I first coded this program, I wrote it in the manner presented over the past few weeks, one step at a time. I feel that the best way to write something weird like managing pointers is to do so incrementally. The final step is to add values to each of the allocated sticks and then output the values once everything is allocated.
The following code adds two items to the code shown in last week’s Lesson: statements to assign the values and statements to output the results. Both use a similar oddball expression to access the individual values, which is the point of this Lesson.
2025_10_25-Lesson.c
#include <stdio.h> #include <stdlib.h> #include <time.h> /* allocate storage for ten integers */ int *ten(void) { int *iptr = NULL; iptr = malloc( sizeof(int) * 10 ); return(iptr); } int main() { int **tenint = NULL; int x,y; /* seed the randomizer */ srand( (unsigned)time(NULL) ); /* allocate 10 sticks of 10 integers */ for( y=0; y<10; y++ ) { /* allocate first stick */ if( tenint==NULL ) { tenint = malloc( sizeof( int *) ); if( tenint==NULL ) { fprintf(stderr,"Unable to allocate storage\n"); exit(1); } } else /* allocate additional sticks */ { tenint = realloc(tenint, sizeof( int *) * (y+1) ); if( tenint==NULL ) { fprintf(stderr,"Unable to reallocate storage\n"); exit(1); } } /* get the storage */ *(tenint+y) = ten(); if( *(tenint+y)==NULL ) { fprintf(stderr,"Unable to allocate stick %d\n",y); exit(1); } printf("Stick %d allocated\n",y); /* assign the values */ for( x=0; x<10; x++ ) *(*(tenint+y)+x) = rand() % 10 + 10*(y+1); } /* output the results */ for( y=0; y<10; y++ ) { printf("Stick %d: ",y); for( x=0; x<10; x++ ) printf("%3d ",*(*(tenint+y)+x) ); putchar('\n'); } /* clean-up */ for( y=0; y<10; y++ ) free( *(tenint+y) ); free(tenint); puts("All sticks freed"); return 0; }
The first new bit of code appears inside the nested loop, under the comment /* assign the values */:
for( x=0; x<10; x++ )
*(*(tenint+y)+x) = rand() % 10 + 10*(y+1);
The right side of the expression generates a random value based on the value of variable y. The left side of the equation features this contraption:
*(*(tenint+y)+x)
As with anything ugly in programming, it helps to work from the inside out.
tenint is the address were a series of pointers are stored.
tenint+y is an offset into the buffer where the pointers are stored. It represents addresses (pointers) zero through y, as stored in the tenint buffer.
*(tenint+y) is the address stored at offset y. So if y==3, *(tenint+y) represents the fourth value (address) stored in the buffer, the location of a ten-integer stick in memory. An equivalent in array notation would be tenint[y]. (But remember that array notation isn’t a substitute for using pointers.)
*(tenint+y)+x is an offset from the address *(tenint+y). The result is still an address, but at an offset within the stick where the ten integers are stored. For example, if x==7, *(tenint+y)+x is the address of the eighth integer in the stick. The array notation equivalent would be tenint[y][x]. This value is still an address.
*(*(tenint+y)+x) is a value, the integer, stored at address *(tenint+y)+x.
As the value of x increases, each subsequent integer in the stick is fetched. Then the outer y loop repeats to reference the next stick stored in the tenint buffer.
Here’s the program’s output:
Stick 0 allocated
Stick 1 allocated
Stick 2 allocated
Stick 3 allocated
Stick 4 allocated
Stick 5 allocated
Stick 6 allocated
Stick 7 allocated
Stick 8 allocated
Stick 9 allocated
Stick 0: 14 19 10 15 18 14 15 17 16 14
Stick 1: 22 26 21 29 23 22 20 20 29 23
Stick 2: 38 36 33 30 36 32 32 37 37 34
Stick 3: 46 43 45 48 40 43 44 46 40 40
Stick 4: 52 54 58 55 56 52 58 56 54 59
Stick 5: 60 65 67 65 65 64 67 69 63 66
Stick 6: 75 71 71 70 79 71 75 75 77 75
Stick 7: 85 81 82 84 87 88 88 85 84 82
Stick 8: 96 94 99 93 99 95 97 98 96 91
Stick 9: 104 102 102 105 102 104 109 108 109 106
All sticks freed
The point to all this is to manage a collection of allocated addresses, expanding this collection as new sticks are added. A more common way to deal with such an operation might be to create a linked list. But my point is more to teach the double-pointer concept when it comes to allocating, managing, and accessing storage.