Nothing paralyzes a C programmer like double-asterisk notation. What does it mean? Can you use it? How is it passed to a function and then referenced? I, too, fall victim to this confusion. So a good explanation is in order.
Double-pointer notation is best demonstrated with an array of strings:
char *words[] = {
"alpha", "beta", "gamma", "delta", NULL
};
This declaration represents an array of pointers, the memory locations of the various strings where they’re stored in memory. (I added NULL as the last item to identify the end of the list.)
You can use array notation to reference each string: words[1]
is beta
, for example. You can also use pointer notation: *(words+1)
is beta
.
When passing the array to a function, you can use words
or *words
. What’s passed and how the function deals with it is demonstrated in this code:
2024_07_20-Lesson.c
#include <stdio.h> void one(char **words) { /* **words is the base of the array **words is also the first string's first character *words is the first string */ puts("You have passed the entire array:"); while(*words) puts(*words++); } void two(char *word) { /* word is a string *word is the first character of the string */ puts("You have passed a single string:"); puts(word); } int main() { char *words[] = { "alpha", "beta", "gamma", "delta", NULL }; one(words); two(*words); return 0; }
Function one() is passed words
, which is the base of the pointer array. In the function’s declaration, the argument is written **words
. The reason is that words
in the main() function is the base of the array. In the one() function, words
represents an array of pointers, which is why it’s referenced as **words
.
Within the one() function, variable *words
represents a string in the array. The while loop outputs each word. If you use **words
in the function, it represents the first character in the first string. Remember, **words
in the function’s declaration describes the type of data passed. In the function, the double-asterisk represents a pointer-pointer: the address of the first string and the address of its first character.
The two() function is passed *words
, which in the main() function represents a single string. For the two() function declaration it represents a single string, *word
. Within the function, word
represents the string; *word
is the first character of the string.
All this asterisk nonsense can be so confusing! It’s easy to understand how any C programmer, not just a beginner, can be driven nuts with this stuff.
In the spirit of “A picture is worth a thousand words” I always recommend people to make a drawing (I hope this works out with the blog software used here; I relied on Uɴɪᴄᴏᴅᴇ Box Drawing characters to create this beauty):
words
│
│ char **
│
V
┌────────────────┐ char
│ │ ┌───┬───┬───┬───┬───┬───┐
│ [0] char * —-│–>│ a │ l │ p │ h │ a │ 0 │
│ │ └───┴───┴───┴───┴───┴───┘
├────────────────┤ char
│ │ ┌───┬───┬───┬───┬───┐
│ [1] char * —-│–>│ b │ e │ t │ a │ 0 │
│ │ └───┴───┴───┴───┴───┘
├────────────────┤
│ : │
│ : │
│ : │
├────────────────┤
│ │
│ [n-1] char * –│–> NULL
│ │
└────────────────┘
Many programmers, for reasons not quite clear to me, often seem to resist this idea at first. When one takes the time to make a drawing of an envisioned data structure everything becomes quite a lot easier, however.
As every level of indirection adds a
*
and every*
prepended in front of a pointer removes one level of indirection, it should then be quite clear how certain parts of a data structure at hand are to be accessed.I’m glad you did the graphic. When I read this post before publishing, I wished I would have added a graphic image, so thanks!
Back when I learned Assembly language, a memory map helped me to understand what was going on. The bracket notation in Assembly is what pointer notation is in C. Once I made the connection, understanding pointers became easier.
Curiously enough, I’m currently reading a book on Visual Thinking by Temple Grandin. She explains a lot about visual learning, so it completely makes sense to illustrate the concept of pointers to help people understand.
I think if you put the characters making up the diagram inside <code></code> it’ll use a monospace font and line up better.
It would be quite easy to write a function that takes an array as an argument and creates a diagram like M. Stumpfl’s, with the actual array data.
(I should know better than to say things like “it would be quite easy” :))
dgookin: glad you like my attempt at some ASCII art, even if it is flawed. (I put it into a <pre>…</pre> block, but this didnʼt have the expected effect; anyway, it still getʼs the idea across, I think ☺)
Chris Webb: an interesting idea, that should—I agree—be doable quite easily… at least for one- and two-dimensional arrays. In even higher dimensions the question “Where to put it?” would become a pressing one quite fast, me thinks 🙂