45
votes

When does using pointers in any language require someone to use more than one, let's say a triple pointer. When does it make sense to use a triple pointer instead of just using a regular pointer?

For example:

char  * * *ptr;

instead of

char *ptr;
17
This is a very indirect question.James McNellis
You may enjoy cdeclbobobobo

17 Answers

56
votes

each star should be read as "which pointed to by a pointer" so

char *foo;

is "char which pointed to by a pointer foo". However

char *** foo;

is "char which pointed to by a pointer which is pointed to a pointer which is pointed to a pointer foo". Thus foo is a pointer. At that address is a second pointer. At the address pointed to by that is a third pointer. Dereferencing the third pointer results in a char. If that's all there is to it, its hard to make much of a case for that.

Its still possible to get some useful work done, though. Imagine we're writing a substitute for bash, or some other process control program. We want to manage our processes' invocations in an object oriented way...

struct invocation {
    char* command; // command to invoke the subprocess
    char* path; // path to executable
    char** env; // environment variables passed to the subprocess
    ...
}

But we want to do something fancy. We want to have a way to browse all of the different sets of environment variables as seen by each subprocess. to do that, we gather each set of env members from the invocation instances into an array env_list and pass it to the function that deals with that:

void browse_env(size_t envc, char*** env_list);
11
votes

If you work with "objects" in C, you probably have this:

struct customer {
    char *name;
    char *address;
    int id;
} typedef Customer;

If you want to create an object, you would do something like this:

Customer *customer = malloc(sizeof Customer);
// Initialise state.

We're using a pointer to a struct here because struct arguments are passed by value and we need to work with one object. (Also: Objective-C, an object-oriented wrapper language for C, uses internally but visibly pointers to structs.)

If I need to store multiple objects, I use an array:

Customer **customers = malloc(sizeof(Customer *) * 10);
int customerCount = 0;

Since an array variable in C points to the first item, I use a pointer… again. Now I have double pointers.

But now imagine I have a function which filters the array and returns a new one. But imagine it can't via the return mechanism because it must return an error code—my function accesses a database. I need to do it through a by-reference argument. This is my function's signature:

int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount);

The function takes an array of customers and returns a reference to an array of customers (which are pointers to a struct). It also takes the number of customers and returns the number of filtered customers (again, by-reference argument).

I can call it this way:

Customer **result, int n = 0;
int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n);

I could go on imagining more situations… This one is without the typedef:

int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns);

Obviously, I would be a horrible and/or sadistic developer to leave it that way. So, using:

typedef Customer *CustomerArray;
typedef CustomerArray *CustomerMatrix;

I can just do this:

int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns);

If your app is used in a hotel where you use a matrix per level, you'll probably need an array to a matrix:

int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels);

Or just this:

typedef CustomerMatrix *Hotel;
int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels);

Don't get me even started on an array of hotels:

int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels);

…arranged in a matrix (some kind of large hotel corporation?):

int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns);

What I'm trying to say is that you can imagine crazy applications for multiple indirections. Just make sure you use typedef if multi-pointers are a good idea and you decide to use them.

(Does this post count as an application for a SevenStarDeveloper?)

5
votes

A pointer is simply a variable that holds a memory address.

So you use a pointer to a pointer, when you want to hold the address of a pointer variable.

If you want to return a pointer, and you are already using the return variable for something, you will pass in the address of a pointer. The function then dereferences this pointer so it can set the pointer value. I.e. the parameter of that function would be a pointer to a pointer.

Multiple levels of indirection are also used for multi dimensional arrays. If you want to return a 2 dimensional array, you would use a triple pointer. When using them for multi dimensional arrays though be careful to cast properly as you go through each level of indirection.

Here is an example of returning a pointer value via a parameter:

//Not a very useful example, but shows what I mean...
bool getOffsetBy3Pointer(const char *pInput, char **pOutput)
{
  *pOutput = pInput + 3;
  return true;
}

And you call this function like so:

const char *p = "hi you";
char *pYou;
bool bSuccess = getOffsetBy3Pointer(p, &pYou);
assert(!stricmp(pYou, "you"));
5
votes

ImageMagicks's Wand has a function that is declared as

WandExport char* * * * * * DrawGetVectorGraphics    (  const DrawingWand  *) 

I am not making this up.

4
votes

N-dimensional dynamically-allocated arrays, where N > 3, require three or more levels of indirection in C.

3
votes

A standard use of double pointers, eg: myStruct** ptrptr, is as a pointer to a pointer. Eg as a function parameter, this allows you to change the actual structure the caller is pointing to, instead of only being able to change the values within that structure.

3
votes

Char *** foo can be interpreted as a pointer to a two-dimensional array of strings.

2
votes

You use an extra level of indirection - or pointing - when necessary, not because it would be fun. You seldom see triple pointers; I don't think I've ever seen a quadruple pointer (and my mind would boggle if I did).

State tables can be represented by a 2D array of an appropriate data type (pointers to a structure, for example). When I wrote some almost generic code to do state tables, I remember having one function that took a triple pointer - which represented a 2D array of pointers to structures. Ouch!

1
votes
 int main( int argc, char** argv );
1
votes

Functions that encapsulate creation of resources often use double pointers. That is, you pass in the address of a pointer to a resource. The function can then create the resource in question, and set the pointer to point to it. This is only possible if it has the address of the pointer in question, so it must be a double pointer.

0
votes

If you have to modify a pointer inside a function you must pass a reference to it.

0
votes

It makes sense to use a pointer to a pointer whenever the pointer actually points towards a pointer (this chain is unlimited, hence "triple pointers" etc are possible).

The reason for creating such code is because you want the compiler/interpreter to be able to properly check the types you are using (prevent mystery bugs).

You dont have to use such types - you can always simply use a simple "void *" and typecast whenever you need to actually dereference the pointer and access the data that the pointer is directing towards. But that is usually bad practice and prone to errors - certainly there are cases where using void * is actually good and making code much more elegant. Think of it more like your last resort.

=> Its mostly for helping the compiler to make sure things are used the way they are supposed to be used.

0
votes

To be honest, I've rarely seen a triple-pointer.

I glanced on google code search, and there are some examples, but not very illuminating. (see links at end - SO doesn't like 'em)

As others have mentioned, double pointers you'll see from time to time. Plain single pointers are useful because they point to some allocated resource. Double pointers are useful because you can pass them to a function and have the function fill in the "plain" pointer for you.

It sounds like maybe you need some explanation about what pointers are and how they work? You need to understand that first, if you don't already.

But that's a separate question (:

http://www.google.com/codesearch/p?hl=en#e_ObwTAVPyo/security/nss/lib/ckfw/capi/ckcapi.h&q=***%20lang:c&l=301

http://www.google.com/codesearch/p?hl=en#eVvq2YWVpsY/openssl-0.9.8e/crypto/ec/ec_mult.c&q=***%20lang:c&l=344

0
votes

Pointers to pointers are rarely used in C++. They primarily have two uses.

The first use is to pass an array. char**, for instance, is a pointer to pointer to char, which is often used to pass an array of strings. Pointers to arrays don't work for good reasons, but that's a different topic (see the comp.lang.c FAQ if you want to know more). In some rare cases, you may see a third * used for an array of arrays, but it's commonly more effective to store everything in one contiguous array and index it manually (e.g. array[x+y*width] rather than array[x][y]). In C++, however, this is far less common because of container classes.

The second use is to pass by reference. An int* parameter allows the function to modify the integer pointed to by the calling function, and is commonly used to provide multiple return values. This pattern of passing parameters by reference to allow multiple returns is still present in C++, but, like other uses of pass-by-reference, is generally superseded by the introduction of actual references. The other reason for pass-by-reference - avoiding copying of complex constructs - is also possible with the C++ reference.

C++ has a third factor which reduces the use of multiple pointers: it has string. A reference to string might take the type char** in C, so that the function can change the address of the string variable it's passed, but in C++, we usually see string& instead.

0
votes

When you use nested dynamically allocated (or pointer linked) data structures. These things are all linked by pointers.

0
votes

Particularly in single-threaded dialects of C which don't aggressively use type-based aliasing analysis, it can sometimes be useful to write memory managers which can accommodate relocatable objects. Instead of giving applications direct pointers to chunks of memory, the application receives pointers into a table of handle descriptors, each of which contains a pointer to an actual chunk of memory along with a word indicating its size. If one needs to allocate space for a struct woozle, one could say:

struct woozle **my_woozle = newHandle(sizeof struct woozle);

and then access (somewhat awkwardly in C syntax--the syntax is cleaner in Pascal): (*my_woozle)->someField=23; it's important that applications not keep direct pointers to any handle's target across calls to functions which allocate memory, but if there only exists a single pointer to every block identified by a handle the memory manager will be able to move things around in case fragmentation would become a problem.

The approach doesn't work nearly as well in dialects of C which aggressively pursue type-based aliasing, since the pointer returned by NewHandle doesn't identify a pointer of type struct woozle* but instead identifies a pointer of type void*, and even on platforms where those pointer types would have the same representation the Standard doesn't require that implementations interpret a pointer cast as an indication that it should expect that aliasing might occur.

0
votes

Double indirection simplifies many tree-balancing algorithms, where usually one wants to be able to efficiently "unlink" a subtree from its parent. For instance, an AVL tree implementation might use:

void rotateLeft(struct tree **tree) {
    struct tree *t = *tree,
                *r = t->right,
                *rl = r->left;
    *tree = r;
    r->left = t;
    t->right = rl;
}

Without the "double pointer", we would have to do something more complicated, like explicitly keeping track of a node's parent and whether it's a left or right branch.