2

I don't really understand how the pointer to pointer works. Any way to do the same work without using pointer to pointer?

struct customer
{
    char name[20];
    char surname[20];
    int code;
    float money;
};
typedef struct customer customer;
void inserts(customer **tmp)
{
    *tmp = (customer *)malloc(sizeof(customer));
    puts("Give me a customer name, surname code and money");
    scanf("%s %s %d %f", (*tmp)->name, (*tmp)->surname, &(*tmp)->code, &(*tmp)->money);
}
Andro
  • 2,232
  • 1
  • 27
  • 40
  • 4
    OT: Don't cast the result of malloc: http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – Barmar May 30 '14 at 20:54
  • You could create `customer* tmp` in the function and return the pointer instead of void. – IllusiveBrian May 30 '14 at 21:00
  • 2
    A pointer is a variable that tells you where to find another variable. If you understand the idea of "pointer to a variable" then you must understand "pointer to a pointer" because that is a special case of it. – M.M May 30 '14 at 22:11

5 Answers5

8

Pointers to Pointers 101

Lets say you have an int variable x: int x;.

  • A portion of memory will be assigned to x large enough to hold an int.
  • The memory assigned to x has an address in the process memory map.
  • To see the address where x is located in memory, use:
    printf("x's memory address = %p\n", (void*)&x);
  • &x means address of x.
  • To see the integer value stored in x, use:
    printf("x=%d\n", x);
  • x can only be manipulated directly within it's scope of existence. For example, it can be manipulated within the function in which it is declared:
    x = 42;
  • Outside its scope of definition, the value of x can be manipulated if its memory address is known.
  • If the value of x (ie: 42) is passed to a function(x), that function cannot manipulate the value of x.
  • If the address of x is passed to a function(&x), that function can manipulate the value of x.

Now, lets say that you have a pointer variable p that assumes it points to an int: int *p;

  • A portion of memory will be assigned to p large enough to hold a memory address.
  • The memory assigned to p has an address in the process memory map.
  • &p means address of p.
  • To see the address where p is located in memory, use:
    printf("p's memory addr = %p\n", &p);
  • To see the address where p is pointing, use:
    printf("Address where p is pointing: %p\n", p);
  • To see the alleged integer being pointed to, use:
    printf("int = %d\n", *p);
  • The value of p is an address in memory; and p can be set to point to any address, whether or not that address actually exists in the process memory map.
  • The address of an object is a pointer to that object. Hence, To cause p to point to x:
    p = &x
  • Whatever p is pointing at can be referred to by the pointer type (type int for p).
  • The amount of memory assigned to p will vary, depending on the architecture of the machine; and how it represents an address.
  • p can only be manipulated directly within it's scope of existence. For example, it can be manipulated within the function in which it is declared:
    p = &x; or p = NULL
  • Outside its scope of existence, the value of p can be manipulated if its' memory address is known.
  • If the value of p (an address) is passed to a function(p), that function cannot manipulate 'p' to change where it points.
  • If the address of p is passed to a function(&p), that function can manipulate what p points to.

Now, lets say that you have a pointer to a pointer variable pp that assumes it points to a pointer to an 'int': int **pp; ...or: void inserts(customer **tmp) :

  • The address of a pointer is a pointer to a pointer.
  • ...

enter image description here

Back to the question

Q: Any way to do the same work without using pointer to pointer?

No. Assume the following:

void inserts(customer **tmp);

...
   {
   customer cust;
   custPtr  custPtr = &cust;

   inserts(&custPtr);
   ... 

The inserts() function requires the address of a pointer in order to manipulate where custPtr points.

If instead:

void inserts2(customer *tmp);

...
   {
   customer cust;
   custPtr  custPtr = &cust;

   inserts2(custPtr);
   ... 

The insert2() would get a copy of the value of custPtr, which is the address of cust. Hence, insert2() could modify the value(s) of cust, but could not change where custPtr is pointing.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Mahonri Moriancumer
  • 5,993
  • 2
  • 18
  • 28
4

For any parameter of type T, if you want to modify the value of the parameter and have that change reflected in the caller, you must pass a pointer:

void foo( T *ptr ) 
{
  *ptr = new_value();
}

void bar( void )
{
  T var;
  foo( &var ); // writes to var
}

If T is a pointer type Q *, then you wind up with a pointer to a pointer:

void foo( Q **ptr )
{
  *ptr = new_value();
}

void bar( void )
{
  Q *var;
  foo( &var ); // writes to var
}

You can use typedefs to hide the pointerness of the variable, but in my experience hiding pointers behind typedefs is bad juju.

You could change your function to return the pointer value instead of writing to the parameter, as @Namfuak suggests:

Q *foo( void )
{
  Q *val = new_value();
  return val;
}

void bar( void )
{
  Q *var;
  var = foo();
}
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • 1
    +1 for advice on not hiding pointers behind typedefs. It's horror for those who have to read the such code after the developer. – Andro May 30 '14 at 22:23
1

Multiple levels of indirection can cause confusion. A good strategy is to reduce the confusion, by reducing the levels of indirection.

Legitimate concerns with using scanf() aside, the following would be a way to achieve this:

void inserts(customer **tmp)
{
    customer *new_cust = malloc(sizeof *new_cust);
    if ( !new_cust ) {
        fprintf(stderr, "Couldn't allocate memory.\n");
        exit(EXIT_FAILURE);
    }

    puts("Give me a customer name, surname code and money");
    scanf("%s %s %d %f", new_cust->name, new_cust->surname,
                         new_cust->code, new_cust->money);

    /*  We delay using multiple levels of indirection
        until we get to the very last line, here.      */

    *tmp = new_cust;
}

As others have mentioned, in this particular case just returning the pointer would likely be easier.

Crowman
  • 25,242
  • 5
  • 48
  • 56
0

If a pointer is your phonebook entry, a pointer to that pointer can be thought of telling you the phonebook in which that entry exists.

Looking at your code:

struct customer {
    char name[20];
    char surname[20];
    int code;
    float money;
};

First, you should not use a float type for money.

Your question is related to C FAQ 4.8. Basically, you have a function to insert a customer record. Where do you want to insert the record? Something that holds the record obviously, a phonebook, a database etc. So, what you need is a pointer to something that holds pointers pointers to the objects you want to insert.

Now, as for the rest of your code, first, note that you should not cast the return value of malloc. Second, using scanf is a serious security risk. Third, you are allocating a new customer record in the inserts function, but you have no way to communicate any failure to the code that calls your function.

Arguably, the name of the function should be something like create_customer_interactive to convey that it does more than just insert a record in a list of records.

Here is how one might structure your code:

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

struct customer {
    char *name;
    char *surname;
    int code;
    int money; /* in cents */
};

typedef struct customer *PCustomer;

int read_customer_record(FILE *fp, PCustomer customer) {
    /* dummy implementation */
    customer->name = "A. Tester";
    customer->surname = "McDonald";
    customer->code = 123;
    customer->money = 100 * 100;
    return 1;
}

PCustomer insert_record_interactive(PCustomer *db, FILE *fp) {
    PCustomer tmp = malloc(sizeof(*tmp));
    if (!tmp) {
        return NULL;
    }
    if (!read_customer_record(fp, tmp)) {
        free(tmp);
        return NULL;
    }
    *db = tmp;
    return tmp;
}

int main(void) {
    PCustomer new_customer;
    PCustomer *db = malloc(3 * sizeof(*db));
    if (!db) {
        perror("Failed to allocate room for customer records");
        exit(EXIT_FAILURE);
    }

    /* insert a record in the second slot */
    new_customer = insert_record_interactive(&db[1], stdin);
    if (!new_customer) {
        perror("Failed to read customer record");
        exit(EXIT_FAILURE);
    }

    printf(
            "%s %s (%d) : $%.2f\n",
            new_customer->name,
            new_customer->surname,
            new_customer->code,
            ((double)new_customer->money) / 100.0
            );
    return 0;
}
Community
  • 1
  • 1
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
-2
void inserts(customer *tmp[])
{
    *tmp = malloc(sizeof(customer));
    puts("Give me a customer name, surname code and money");
    scanf("%s %s %d %f", (*tmp)->name, (*tmp)->surname, &(*tmp)->code, &(*tmp)->money);
}

That is equivalent. A pointer to a pointer can also be expressed as a pointer to an unknown-sized array.

What happens is that *tmp gets assigned to an array of 1 elements of type "customer". tmp itself refers in both ways (**tmp and *tmp[]) to the pointer of an unknown-sized array of "customer" elements.

user3691920
  • 31
  • 1
  • 3
  • `tmp` is still a pointer to a pointer, it is exactly the same as whether you write `**tmp` or `*tmp[]` in the parameter list. (This is a syntax quirk that only applies in parameter lists). Your second paragraph is bogus, `*tmp` does not get assigned to anything. `tmp` still points to whatever the provided argument pointed to when the function was called. – M.M May 30 '14 at 22:10
  • *tmp gets assigned to malloc(sizeof(customer)) in both cases. tmp points to the same location, of course. – user3691920 May 30 '14 at 22:41
  • Oh, you're using "gets assigned" backwards. In this code the result of malloc gets assigned to `*tmp` (not the other way around). – M.M May 30 '14 at 22:42
  • No, i don't get it backwards. *tmp is assigned to the result of malloc, and **tmp / *tmp[] points to *tmp of course. What is your point? – user3691920 May 30 '14 at 22:44
  • 1
    Possibly a language issue - you could say that `*tmp` is *assigned* the result of `malloc()`, but you can't say that `*tmp` is assigned *to* the result of `malloc()`. As in, "the student was assigned the project", as opposed to "the professor assigned the project to the student". – Crowman May 30 '14 at 22:47
  • Ok, i am not a native english speaker, sorry for the confusion. Nevertheless the original question was how to replace **tmp by an array type, and i tried to answer that. – user3691920 May 30 '14 at 22:49
  • My main point is that your answer makes it seem like there is some sort of difference between the meaning of `**tmp` and `*tmp[]` which end up having the same effect because a 1-sized array is like a single object. (which is not true, `tmp` is not an array type in either case) – M.M May 30 '14 at 22:52
  • Nope, of course there is not a difference. But that's how i understood the original question. "I don't really understand how the pointer to pointer works. Any way to do the same work without using pointer to pointer?" – user3691920 May 30 '14 at 22:54
  • Your code still uses a pointer to pointer though. `tmp` is a pointer to pointer in your code. – M.M May 30 '14 at 23:00
  • Didn't i exactly state that? – user3691920 May 30 '14 at 23:04
  • I'd really like if you would cancel that downvote, since it isn't justified. Thanks! – user3691920 May 30 '14 at 23:12
  • This answer is only muddling the issue by conflating a pointer to pointer with an array of pointers. This does NOT help in understanding the matter. In fact, confusing pointer-to-X with array-of-X is a frequent problem in using and teaching C, to resolve which C++ specifically introduced the concept of references (with limited success). – user4815162342 May 31 '14 at 14:12