Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does having two asterisk ** in Objective-C mean?

I understand having one asterisk * is a pointer, what does having two ** mean?

I stumble upon this from the documentation:

- (NSAppleEventDescriptor *)executeAndReturnError:(NSDictionary **)errorInfo
like image 894
John Avatar asked May 07 '09 06:05

John


2 Answers

It's a pointer to a pointer, just like in C (which, despite its strange square-bracket syntax, Objective-C is based on):

char c;
char *pc = &c;
char **ppc = &pc;
char ***pppc = &ppc;

and so on, ad infinitum (or until you run out of variable space).

It's often used to pass a pointer to a function that must be able to change the pointer itself (such as re-allocating memory for a variable-sized object).

=====

Following your request for a sample that shows how to use it, here's some code I wrote for another post which illustrates it. It's an appendStr() function which manages its own allocations (you still have to free the final version). Initially you set the string (char *) to NULL and the function itself will allocate space as needed.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void appendToStr (int *sz, char **str, char *app) {
    char *newstr;
    int reqsz;

    /* If no string yet, create it with a bit of space. */

    if (*str == NULL) {
        *sz = strlen (app) + 10;
        if ((*str = malloc (*sz)) == NULL) {
            *sz = 0;
            return;
        }
        strcpy (*str, app);
        return;
    }

 

    /* If not enough room in string, expand it. We could use realloc
       but I've kept it as malloc/cpy/free to ensure the address
       changes (for the program output). */

    reqsz = strlen (*str) + strlen (app) + 1;
    if (reqsz > *sz) {
        *sz = reqsz + 10;
        if ((newstr = malloc (*sz)) == NULL) {
            free (*str);
            *str = NULL;
            *sz = 0;
            return;
        }
        strcpy (newstr, *str);
        free (*str);
        *str = newstr;
    }

    /* Append the desired string to the (now) long-enough buffer. */

    strcat (*str, app);
}

 

static void dump(int sz, char *x) {
    if (x == NULL)
        printf ("%8p   [%2d]   %3d   [%s]\n", x, sz, 0, "");
    else
        printf ("%8p   [%2d]   %3d   [%s]\n", x, sz, strlen (x), x);
}

static char *arr[] = {"Hello.", " My", " name", " is", " Pax",
                      " and"," I", " am", " old."};

int main (void) {
    int i;
    char *x = NULL;
    int sz = 0;

    printf (" Pointer   Size   Len   Value\n");
    printf (" -------   ----   ---   -----\n");
    dump (sz, x);
    for (i = 0; i < sizeof (arr) / sizeof (arr[0]); i++) {
        appendToStr (&sz, &x, arr[i]);
        dump (sz, x);
    }
}

The code outputs the following. You can see how the pointer changes when the currently allocated memory runs out of space for the expanded string (at the comments):

 Pointer   Size   Len   Value
 -------   ----   ---   -----
# NULL pointer here since we've not yet put anything in.
     0x0   [ 0]     0   []

# The first time we put in something, we allocate space (+10 chars).
0x6701b8   [16]     6   [Hello.]
0x6701b8   [16]     9   [Hello. My]
0x6701b8   [16]    14   [Hello. My name]

# Adding " is" takes length to 17 so we need more space.
0x6701d0   [28]    17   [Hello. My name is]
0x6701d0   [28]    21   [Hello. My name is Pax]
0x6701d0   [28]    25   [Hello. My name is Pax and]
0x6701d0   [28]    27   [Hello. My name is Pax and I]

# Ditto for adding " am".
0x6701f0   [41]    30   [Hello. My name is Pax and I am]
0x6701f0   [41]    35   [Hello. My name is Pax and I am old.]

In that case, you pass in **str since you need to be able to change the *str value.

=====

Or the following, which does an unrolled bubble sort (oh, the shame!) on strings that aren't in an array. It does this by directly exchanging the addresses of the strings.

#include <stdio.h>

static void sort (char **s1, char **s2, char **s3, char **s4, char **s5) {
    char *t;

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
    if (strcmp (*s2, *s3) > 0) { t = *s2; *s2 = *s3; *s3 = t; }
    if (strcmp (*s3, *s4) > 0) { t = *s3; *s3 = *s4; *s4 = t; }
    if (strcmp (*s4, *s5) > 0) { t = *s4; *s4 = *s5; *s5 = t; }

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
    if (strcmp (*s2, *s3) > 0) { t = *s2; *s2 = *s3; *s3 = t; }
    if (strcmp (*s3, *s4) > 0) { t = *s3; *s3 = *s4; *s4 = t; }

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
    if (strcmp (*s2, *s3) > 0) { t = *s2; *s2 = *s3; *s3 = t; }

    if (strcmp (*s1, *s2) > 0) { t = *s1; *s1 = *s2; *s2 = t; }
}

int main (int argCount, char *argVar[]) {
    char *a = "77";
    char *b = "55";
    char *c = "99";
    char *d = "88";
    char *e = "66";

    printf ("Unsorted: [%s] [%s] [%s] [%s] [%s]\n", a, b, c, d, e);
    sort (&a,&b,&c,&d,&e);
    printf ("  Sorted: [%s] [%s] [%s] [%s] [%s]\n", a, b, c, d, e);
    return 0;
}

which produces:

Unsorted: [77] [55] [99] [88] [66]
  Sorted: [55] [66] [77] [88] [99]

Never mind the implementation of sort, just notice that the variables are passed as char ** so that they can be swapped easily. Any real sort would probably be acting on a true array of data rather than individual variables but that's not the point of the example.

like image 144
paxdiablo Avatar answered Oct 05 '22 09:10

paxdiablo


Pointer to Pointer

The definition of "pointer" says that it's a special variable that stores the address of another variable (not the value). That other variable can very well be a pointer. This means that it's perfectly legal for a pointer to be pointing to another pointer.

Let's suppose we have a pointer p1 that points to yet another pointer p2 that points to a character c. In memory, the three variables can be visualized as :

enter image description here

So we can see that in memory, pointer p1 holds the address of pointer p2. Pointer p2 holds the address of character c.

So p2 is pointer to character c, while p1 is pointer to p2. Or we can also say that p2 is a pointer to a pointer to character c.

Now, in code p2 can be declared as :

char *p2 = &c;

But p1 is declared as :

char **p1 = &p2;

So we see that p1 is a double pointer (i.e. pointer to a pointer to a character) and hence the two *s in declaration.

Now,

  • p1 is the address of p2 i.e. 5000
  • *p1 is the value held by p2 i.e. 8000
  • **p1 is the value at 8000 i.e. c I think that should pretty much clear the concept, lets take a small example :

Source: http://www.thegeekstuff.com/2012/01/advanced-c-pointers/

For some of its use cases:

This is usually used to pass a pointer to a function that must be able to change the pointer itself, some of its use cases are:

  • Such as handling errors, it allows the receiving method to control what the pointer is referencing to. See this question
  • For creating an opaque struct i.e. so that others won't be able to allocate space. See this question
  • In case of memory expansion mentioned in the other answers of this question.

feel free to edit/improve this answer as I am learning:]

like image 24
mfaani Avatar answered Oct 05 '22 10:10

mfaani