Some notes on C: Pointers to Pointers

September 20th, 2008

I’ve finally submitted my last assignment for my subject in Programming, using C. This practically marks the end of the semester. All that’s left is taking the final examinations on the 27th. I can only imagine how uncomfortable that will be since we’ll be practically programming on paper only.

I have mixed feelings about how this particular course has gone. For starters, we were “required” to use Turbo C. Well actually we were allowed to use something else as long as they display and compile properly on TC, which means setting text editors to use CR\LF as end of lines (DOS style) and testing them in TC (of course I used GCC (g++) and beloved Kate). I don’t know why it was the recommended IDE and compiler, though, since I think it sucks on both aspects. Other than that, I’m quite satisfied with the course. I’ve come across some algorithm issues in my assignments that I haven’t really thought much about before, so I had a chance to flex my brain muscles and understand some aspects of the language much better. One of those “Eureka!” moments.

Pointers to Pointers

In one of the assignments, our teacher made some notes about this topic which the book failed to cover. A pointer to a pointer, which in turn points to something else, might sound useless, but there’s definitely one use case we encountered that needed it: passing pointers and modifying them in functions.

Simple case: You have a generic function for opening files and checking for errors. Normally you’d declare a pointer to a file in main() and pass it to the function, like so:

int openfile(FILE *fileptr, char *name, char *mode)
{
    fileptr = fopen(name, mode);

    ...
}

int main()
{
    FILE *fp;

    openfile(fp, "foo", "r");

    ...
}

You’d think that would work, but it doesn’t. In fact, it would error when you try to use that file pointer. Because the address of the opened file isn’t getting assigned to fp in main(), although it was getting assigned to fileptr of course. It wasn’t clear to me at that time. After all, I did pass a pointer, right? Yes, I did, but not for the effect that I thought it would have. I scanned over my old C books and didn’t really see anything that discusses this topic. I have the K & R book, but I haven’t read it much, so I can’t really say. So instead, I’m just going to write this down, maybe to be able to help somebody else in the future (or some of my classmates who might be reading this).

The real issue here is that while pointers are used to pass by reference, the pointers themselves are passed by value. While you can modify the variable that the pointer is pointing to, you cannot modify the pointer itself. It’s probably easier to understand once you see that a pointer is just a variable. It’s a variable who only accepts memory addresses as its value, just like an int would accept integer values, etc. So like any variable, when passed to a function, a copy of that pointer is made and the original pointer’s value is untouched. So when you, for example, assign a memory address like a pointer to a file you opened in openfile(), that address gets assigned to the copy of fp, which if fileptr. Once the function ends, that copy is destroyed and the handle to the file is lost. Same goes for assigning a block of memory for a linked list or making a pointer to the head of a linked list point to something else.

So what do you do to be able to modify the pointer in a function? Simple, just like any other variable, you need to pass the address of that variable to the function and the function should receive that address in a pointer. In this case, you pass the address of a pointer and received with a pointer to a pointer. And to access what it is pointing to, as usual, you will need to use the dereference (*) operator. So the above code will now be like so:

int openfile(FILE **fileptr, char *name, char *mode)
{
    *fileptr = fopen(name, mode);

    ...

}

int main()
{
    FILE *fp;

    openfile(&fp, "foo", "r");

    ...
}

I hope that this could help some poor soul whose brain has been plagued by the same problem. It would probably be easier to have done this in C++, using references (&), though I think even that has its own problems. And anyway, the class was on C, not C++.

So that basically ends my formal C education. Not much was added to what I’ve already learned by myself years ago, except for some of the exercises. Now I feel I need to review my C++. :D

15 Responses to “Some notes on C: Pointers to Pointers”

  1. Lee Says:

    I’m surprised that you’ve known C for years and managed to avoid knowing this. I too have found formal education to fill a lot of tiny gaps I never realised I was suffering from.

    But yes, your pointer understanding sounds exactly right now :)

    p.s.: the other major use case for pointers to pointers is dynamic, multi-dimensional arrays.

  2. AlexH Says:

    I think you’ve sort of helped confuse the issue with the char * parameters.

    If you had done the same with int parameters, it would have made things clearer.

  3. Elio Says:

    When I read your post my first reaction was to say “duh!”. But then I recalled my days at high school when I had a lot of trouble understanding pointers on Turbo Pascal.

    It’s a shame that Turbo C is still used on this day. There already much better tools now.

  4. Andreas Says:

    The kids don’t learn assembler anymore these days!
    If you know assembly programming pointers are trivial. It’s (usually) a register that holds a memory address. If you push the value of that register on the stack as a parameter of course the original won’t be modified.
    By the way, assembler was not a part of my formal education either. It doesn’t look enough like maths to qualify as computer science in Germany, I guess. Bah.

  5. jldugger Says:

    What would really help understanding is a memory diagram, including the stack. One of my undergrad courses was basically a study of how a C compiler worked to produce assembly code. We drew out stacks at deepest levels, “decompiled” code, and learned about a couple arches.

    Technically, all that stuff is handled in registers on optimizing compilers, which makes generating reasonably instructive code hard.

  6. Florian Says:

    Ahh, that brings back memories from when I used to program in C. Not that I miss those days…

  7. b0b Says:

    Congratulation, you blogged about what every half decent C programmer knows…

    Next entry I suggest pointer to pointer to pointer.

  8. AdeBe Says:

    @b0b: shut up!
    @jucato: you wrote about interesting thing which in fact confuses a lot of begginers.
    If you want to know about another interesting things in C (and C++ too) I suggest reading a few articles about writing drivers (or kernel modules).

  9. Rodrigo Rosenfeld Rosas Says:

    I hope this piece of code help to understand how to turn your code clearer when you are dealing with pointers to pointers.

    There are at least 2 years I don’t code C/C++ (I am working with Ruby right now), but I still remember some best practices…

    I compiled the above code with cc -ansi code.c

    code.c:
    #define TYPE int
    #define PTYPE TYPE*
    int test(PTYPE* a){
    *a = (PTYPE) 1;
    }
    int main(int argc, char** argv) {
    PTYPE i=0;
    test(&i);
    if (i == (PTYPE) 1) return 0;
    return -1;
    }

    ./a.out && echo 1

    Much simpler, don’t you think?

  10. Rodrigo Rosenfeld Rosas Says:

    Just one more note:

    if programming in C++ you should use the reference operator instead:

    int test(PTYPE &a){
    a = (PTYPE) 1;
    }
    code:
    PTYPE i=0;
    test(i);

  11. litb Says:

    Rodrigo Rosenfeld Rosas:
    i’m afraid to tell, but your code makes use of #define , when typedef is the right way of doing it:

    void sample(int ** ppint);

    usually in c++ you would do (if you don’t allow passing 0 as a null-pointer):
    void sample(int *& rpint);

    indeed, pointer to function pointers are also possible:
    void sample(void (**pfptr)());

    c++ way of that:
    void sample(void (*&pfptr)());

    and, last but not least, to arrays too:
    void sample(int (**paptr)[42]);

    again, the c++ way:
    void sample(int (*&raptr)[42]);

    note that it doesnt matter how many *’es you have. the following is valid too:
    void sample(int ***** pppppint);

    but, as you cannot have a reference to a reference, not a pointer to a reference (references are not reassignable), the following is NOT possible:
    void sample(int && rrint);

    ok, so far for this elementary c++ course. please, if you have questions, join ##c++ on freenode. we are glad to help you. have fun!

  12. Marius Gedminas Says:

    Ahh, this brings back memories. I discovered pointers to pointers in Turbo Pascal (which had a separate syntax for pass-by-reference) when I needed to manipulate singly-linked lists.

    procedure RemoveFromList(var Head: ^ListNode; Value: Integer);
    var
    P: ^^ListNode;
    begin
    P := @Head;
    while (P^ nil) and (P^^.Value != Value) do
    P := @P^^.Next;
    if P^ nil then
    P^ := P^^.Next;
    end;

  13. anonymous Says:

    I remember the first time I wrapped my head around pointers (ouch). Joel (of Joel on software) says this process is one of the big CS pruners.

    A lot of comments have also come up about references. They are just wrapped up pointers though (syntactic sugar). That is, changing every “&r” to “*r” (preferably “*const r”) and every “r.” to “r->” will produce the exact same assembler (not that I think you should do this).

  14. Jucato Says:

    Whew! Sorry for not having replied ASAP. I was quite swamped over the weekend. Anyway here goes:

    @Lee: There’s a subtle difference between “known C for years” and “learned C years ago”. There was a huge disconnect of about 8 or so years between the last time I picked up my C books and the first time I picked them up again.

    @AlexH: I do realize that now, but I wanted to use an example that was concrete and useful for my classmates. And in that context, there were only two choices: a file-related function or a linked list function. I chose the file example for simplicity.

    @Elio: My first reaction when I finally realized all of it was also “duh!”, followed by a few palm-to-forehead actions. I’m telling you, the experience was annoying, humbling, and enlightening, all at the same time. :)

    @Andreas: I had a chance to learn assembly language back when I studied C. But I did not have a free assembler available, so I sort of shrugged it off as obsolete. Who knew we’d meet face to face again 8 years after? :D

    @jldugger: Our 200-page school textbook did use memory “diagrams” (more like just a series of boxes), but only to show the basic concepts of pointers within a function, not really explaining that much about when pointers are passed to functions. The focus is mostly on passing the address of what the pointer is pointing to. Only after that brief enlightenment does it make better sense now.

    @b0b: Thanks. If every decent C programmer treats less than half decent programmers like me the way you do, then I am glad not to be a decent programmer.

    Luckily, you seem to be the exception, not the rule.

    @AdeBe: Eep! I’m not sure if writing drivers is even within my radar at the moment. But maybe a more in-depth look at GTK+ would yield some equally interesting insights. :D

    @Rodrigo Rosenfeld: Thanks for the tip, but litb is right. typedef would be a more proper way of doing it. In fact, if I had wanted to write “cleaner” code, I would have done what one book did, basically:

    typedef node *nodeptr&
    foo(nodeptr *np&amp);

    Which basically declares ndptr as an alias to a pointer to an nd, and np as a pointer to ndptr, making it a pointer to a pointer to an nd. That would sure be cleaner, but the explanation behind it would be equally hidden.

    @litb: Ugh! I think I want to take back what I said about C++ references making it easier or more understandable :P

    @Marius Gedminas: Haha! I think I’m beginning to be thankful that my knowledge of Pascal stopped short after I was introduced to C (also 8 years ago).

    @anonymous: what Joel said may not be exactly accurate, as litb mentioned (and I can remember from my C++ book). You can’t assign NULL to references nor assign a reference to a reference, so that would make references more (or less) than just wrapped up pointers.

  15. Roger Says:

    Another thing to think about - in all likelihood the line:

    int openfile(FILE **fileptr, char *name, char *mode)

    should actually be:

    int openfile(FILE **fileptr, const char *name, const char *mode)

Leave a Reply