0

I can't understand multiple pointers pointing to the same address and how to solve it. To be specific, I will write a simple code that fits the case.

Objective: store values from a csv file into a dynamic array of pointers to structure.

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

typedef struct data data_t;
struct data
{
    char *name;
    int id;
    double balance;
};

int main(int argc, char **argv)
{
    FILE *fp = NULL;
    data_t **array = (data_t **) malloc(10 * sizeof(data_t *));
    if (argc < 2)
    {
        printf("No file input\n");
        exit(1);
    }
    if (fp = fopen(argv[1], "r") == NULL)
    {
        printf("File not found\n");
        exit(1);
    }
    char buffer[128];
    char delim[2] = ',';
    int index = 0;
    while(fgets(buffer, 128+1, fp))
    {
        data_t customer;
        customer.name = strtok(buffer, delim);
        customer.id = atoi(strtok(NULL, delim));
        customer.balance = atof(strtok(NULL, delim));
        array[index] = (data_t *) malloc(sizeof(data_t));
        array[index] = &customer;
        index += 1;
    }
    fclose(fp);
    fp = NULL;
}

Regardless of some silly mistakes if there exist.

When trying to print the content of the first element of this array, I always get the content of the last element. I think that's the 'pointing to same address' problem but don't know how to fix it. And I can't get the name printed out properly.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • You never allocate storage for `char *name;` And, `strtok()` assigns the same pointer to `customer.name = strtok(buffer, delim);` every time. (they all point to somewhere in `buffer`) So in the end, all `customer.name` end up pointing to the same thing. Instead, allocate storage for each name and `strcpy()` to the allocated storage (or do it all at once with POSIX `strdup()` if you have it -- but it won't be portable) – David C. Rankin Aug 28 '21 at 03:09
  • `while(fgets(buffer, 128, fp))` no `+1` or better `while(fgets(buffer, sizeof buffer, fp))` – David C. Rankin Aug 28 '21 at 03:15
  • @DavidC.Rankin like in the while loop: `name = (char *) malloc(NAMESIZE * sizeof(char))` and then use strcpy() to store a string? – StoneForest Aug 28 '21 at 03:15
  • `char *tmp = strtok(buffer, delim);` then `size_t n = strlen(tmp);` then `customer.name = malloc (n+1);` and `memcpy (customer.name, tmp, n+1);` (don't forget to free all `array[i].name;` when done) – David C. Rankin Aug 28 '21 at 03:17
  • Also `*array[index] = customer;` not `array[index] = &customer;` (or all `array[index]` will point to the same pointer....) – David C. Rankin Aug 28 '21 at 03:23
  • `array[index] = &customer;` stores the address of a local non-static variable. Outside of the `while` scope this address is invalid. You should copy its contents instead: `*array[index] = customer;`. – rslemos Aug 28 '21 at 03:28
  • Also `data_t **array = (data_t **) malloc(10 * sizeof(data_t *));` allocates space for 10 elements of `data_t`. It will overflow if it happens that `fgets` returns `true` 11 times. Better cap it with `while(index < 10 && fgets(...))`. – rslemos Aug 28 '21 at 03:31
  • @DavidC.Rankin I made it! Thank you a lot, David. I think I didn't make a solid interpretation of these contents. Would you kindly recommend some documents that can help me understand them? I would really appreciate that. Thank you anyway, David. – StoneForest Aug 28 '21 at 03:34
  • @rslemos Thanks for your comment, the `*array[index]` problem David just answered, but still appreciate it. And I'm going to reallocate more space for array, just didn't show it here, thanks for reminding me. – StoneForest Aug 28 '21 at 03:38
  • @StoneForest A few links that provide basic discussions of pointers may help. [Difference between char *pp and (char*) p?](https://stackoverflow.com/a/60519053/3422102) and [Pointer to pointer of structs indexing out of bounds(?)...](https://stackoverflow.com/a/60639540/3422102) (ignore the titles, the answers discuss pointer basics) – David C. Rankin Aug 28 '21 at 03:45

3 Answers3

3

Continuing from my comments, you have a large number of small problems.

First, always compile with FULL warnings enabled, and do not accept code until it compiles without warning. To enable warnings add -Wall -Wextra -pedantic to your gcc/clang compile string (also consider adding -Wshadow to warn on shadowed variables). For VS (cl.exe on windows), use /W3. All other compilers will have similar options. Read and understand each warning -- then go fix it.

That would first disclose missing parenthesis for:

    if ((fp = fopen(argv[1], "r")) == NULL)     /* parenthesis req'd */

It would have shown your next error to be with your declaration for delim. strtok() takes a character string as the delimiter list (and it is constant, so correctly):

    const char *delim = ",\n";                  /* don't forget \n */

(note: include '\n' or you will leave a '\n' hanging off your last token)

There is no +1 with the size needed by fgets(). If you use the sizeof the array you are filling, you can't go wrong, e.g.

    while (fgets (buffer, sizeof buffer, fp))

Now we begin to reach your real problems with strtok() and storage for each name. char *name; in each struct is a uninitialized pointer that points nowhere. While you can assign a pointer to it, you don't want to simply assign the same pointer address to all of the .name members or they will all end up pointing to the same place.

Instead, you need to allocate storage for each name and copy the current name to the allocated block of memory preserving that name., e.g.

        data_t customer;
        char *tmp;          /* temporary pointer to use w/strtok() */
        size_t len;
        
        tmp = strtok (buffer, delim);           /* token for name */
        if (!tmp) {                             /* validate token */
            fputs ("error: strtok() name.\n", stderr);
            return 1;
        }
        
        len = strlen (tmp);                     /* get length */
        customer.name = malloc (len + 1);       /* allocate for string */
        if (!customer.name) {                   /* validate EVERY allocation */
            perror ("malloc-customer.name");
            return 1;
        }
        
        memcpy (customer.name, tmp, len + 1);   /* copy tmp to .name */

(note: there is no need to scan for the end-of-string again with strcpy() when you already know the number of characters to copy, just use memcpy() for that)

Again, you have the "assigning the same pointer" problem with array[index]. You want to copy the contents of data_t customer; to array[index]. You allocate storage for the new struct (good job), but then you overwrite the address for the allocated block with the address of &customer creating a memory leak.

Assign the struct, not it's address. The members will be copied to the allocated struct which is what you want, e.g.

        array[index] = malloc (sizeof *array[index]);   /* allocate struct */
        *array[index] = customer;                       /* assign struct */
        index += 1;

(note: the allocation with malloc() uses the dereferenced pointer (e.g. *array[index]) to set the type-size. If you always use the dereferenced pointer instead of a type -- you will never get the type-size wrong. Also, in C, there is no need to cast the return of malloc, it is unnecessary. See: Do I cast the result of malloc? )

Now things should work a little better. Don't forget to free (array[i].name) before you free (array[i]).

Also, as mentioned above, if you have POSIX strdup() you can allocate and copy in a single operation. But note, strdup() isn't standard C, so using it may result in portability problems. However, if you do, you can simply:

        /* if you have POSIX strdup(), you can simply allocate and copy
         * in one operation
         */
        strdup (customer.name, tmp);

But...

        /* since strdup() allocates, you must validate */
        if (!customer.name) {
            perror ("strdup-customer.name");
            return 1;
        }

It saves a line or two of code, but at the price of standard-C portability. (up to you)

Lastly, avoid atoi() and atof() they provide ZERO error checking and will happily accept atoi ("my cow") silently returning 0 without any indication an error occurred. Use strtol() and strtod() instead, or at least sscanf() where you at least get a success or failure indication for the conversion.

Full Example

The other issues that linger are your use of MagicNumbers. Don't use them, instead:

#define NELEM  10       /* if you need a constant, #define one (or more) */
#define MAXB  128

You also need to protect your allocated block of pointers so you don't attempt to access pointers that are not allocated. You do that in your read-loop, e.g.

    /* protect the allocation bounds, you only have NELEM pointers */
    while (index < NELEM && fgets (buffer, sizeof buffer, fp))

A quick revision with all validations included would look something like:

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

#define NELEM  10       /* if you need a constant, #define one (or more) */
#define MAXB  128

typedef struct data data_t;
struct data
{
    char *name;
    int id;
    double balance;
};

int main(int argc, char **argv)
{
    FILE *fp = NULL;
    data_t **array = malloc (NELEM * sizeof *array);
    if (!array) {                               /* validate EVERY allocation */
        perror ("malloc-array");
        return 1;
    }
    
    if (argc < 2) {
        printf("No file input\n");
        exit(1);
    }
    if ((fp = fopen(argv[1], "r")) == NULL) {   /* parenthesis req'd */
        printf("File not found\n");
        exit(1);
    }
    
    char buffer[MAXB];
    const char *delim = ",\n";                  /* don't forget \n */
    int index = 0;
    
    /* protect the allocation bounds, you only have NELEM pointers */
    while (index < NELEM && fgets (buffer, sizeof buffer, fp))
    {
        data_t customer;
        char *tmp;          /* temporary pointer to use w/strtok() */
        size_t len;
        
        tmp = strtok (buffer, delim);           /* token for name */
        if (!tmp) {                             /* validate token */
            fputs ("error: strtok() name.\n", stderr);
            return 1;
        }
        len = strlen (tmp);                     /* get length */
        customer.name = malloc (len + 1);       /* allocate for string */
        if (!customer.name) {                   /* validate EVERY allocation */
            perror ("malloc-customer.name");
            return 1;
        }
        memcpy (customer.name, tmp, len + 1);   /* copy tmp to .name */
        
        if (!(tmp = strtok(NULL, delim))) {     /* token & validations */
            fputs ("error: strtok() - id.\n", stderr);
            return 1;
        }
        /* MINIMAL conversion validation with sscanf() */
        if (sscanf (tmp, "%d", &customer.id) != 1) {
            fputs ("error: invalid integer value - id.\n", stderr);
            return 1;
        }
        
        if (!(tmp = strtok(NULL, delim))) {     /* token & validations */
            fputs ("error: strtok() - balance.\n", stderr);
            return 1;
        }
        if (sscanf (tmp, "%lf", &customer.balance) != 1) {  /* dito */
            fputs ("error: invalid integer value - balance.\n", stderr);
            return 1;
        }
        
        array[index] = malloc (sizeof *array[index]);   /* allocate struct */
        if (!array[index]) {                            /* validate!! */
            perror ("malloc-array[index]");
            return 1;
        }
        
        *array[index] = customer;                       /* assign struct */
        index += 1;
    }
    fclose(fp);
    fp = NULL;      /* not needed, but doesn't hurt anything */
    
    /* use array[], then don't forget to free memory */
    for (int i = 0; i < index; i++) {
        printf ("\nname    : %s\n"
                "id      : %d\n"
                "balance : %.2f\n", 
                array[i]->name, array[i]->id, array[i]->balance);
        free (array[i]->name);      /* free allocated block for name */
        free (array[i]);            /* free allocated array index */
    }
    free (array);                   /* don't forget to free pointers */
}

(note: you don't use assert(), so there is no need to include the header)

Since strtok() can return NULL on failure, you should assign all pointers to tmp and then validate if (!tmp) { fputs ("error: strtok() - name.\n", stderr); return 1; } and the same for ID and balance before using the pointer in the numeric conversion. You should at minimum use a simple sscanf() validation for each numeric conversion as shown above. Ideally, you would use strtol() and strtod() which provides much more information on error.

Good luck with your coding!

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • No worries. We all started coding from the same place. You are getting to the nuts-and-bolts of C, so learning it correctly the first time will save you a lot of grief later. Good luck with your coding. If you have more questions, drop a comment below. Also note, I've left the validation of `array[index] = malloc (sizeof *array[index]);` to you `:)` (e.g. `if (!array[index]) { /* handle error */ }` – David C. Rankin Aug 28 '21 at 03:50
0

Every customer.name are pointing to your buffer[128] array. Allocate a char vector for each new customer.name with malloc for every item in the "array".

0

@David C. Rankin nailed the coffin! That is a perfect answer!

I'll just add my 2 cents (I tried with a comment, but it was too long)

Since your struct data has just 1 variable sized member you can declare it as the last member, as a fixed size array

struct data
{
    int id;
    double balance;
    char name[1]; // this 1 accounts for the null terminator
};

Then you allocate space for your struct only after you know this member's size (here tmp holding the name just returned by strtok):

malloc(size of *array[index] + strlen(tmp));

Then you copy the name as usual (using memcpy or strcpy, never strdup with this technique)

Then you just use your struct as usual.

On the plus side

  • your memory will be less fragmented, and
  • you don't need to free the name member.

The down side:

  • you can't change the name by pointing it elsewhere; and
  • if you need to add other variable sized member, it must be a pointer (you can't apply this technique with more than 1 variable sized member).

PS: take this with a grain of salt because I've not been in the C bandwagon for the last 20 years; this technique might be outdated, so my 2 cents may be rusty.

rslemos
  • 2,454
  • 22
  • 32