0

I have this struct:

(struct.h)

#pragma once

#ifndef STRUCT_H
#define STRUCT_H

typedef struct {
    unsigned int id;
    char *name;
    char *address;
} Struct;

#endif 

I made a dynamic array containing these structs. I also have a loop that puts some data in those structs:

(main.c)

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

int main() {

    int nStructs = 3;

    Struct *structArray = calloc(nStructs, sizeof * structArray);

    char input[30];

    for (int i = 0; i < nStructs; i++) {
        structArray[i].id = i;
        sprintf(input, "name_%d", i);
        structArray[i].name = input;
        sprintf(input, "addr_%d", i);
        structArray[i].address = input;
    }

    for (int i = 0; i < nStructs; i++) {
        printf("\n[%04d]    ID: %d\n", structArray[i].id, structArray[i].id);
        printf("[%04d]  NAME:   %s\n", structArray[i].id, structArray[i].name);
        printf("[%04d]  ADDR:   %s\n", structArray[i].id, structArray[i].address);
    }

    free(structArray);
    return 0;
}

When I try to run this code, the 'int ID' is printed the right way, but the 'char *name' and 'char *address' are all containing the last value from the loop (in this case "addr_3"):

(output)

[0000]  ID:     0
[0000]  NAME:   addr_2
[0000]  ADDR:   addr_2

[0001]  ID:     1
[0001]  NAME:   addr_2
[0001]  ADDR:   addr_2

[0002]  ID:     2
[0002]  NAME:   addr_2
[0002]  ADDR:   addr_2

I tried to use the debugger in visual studio and it looks like the values are ok when they are put in the array (first for loop), but that they get overwritten at some point. Since im new to visual studio, I don't know how to see the values of the array at a specific point and not just at the value [i].

What am i doing wrong here?

EDIT: I copy/pasted this code from my main project so only the part that im testing will be in the question. But I see I forgot to add 'free(structArray);' above 'return 0;'. Its added in the question now so others don't make the same mistake.

2 Answers2

1

Almost had an example ready but then I noticed your comment.

No, calloc allocates space for your structures, but not strings - your structure only stores pointers.

If you don't want to change the struct, you should allocate memory for each string and then copy it instead of storing the pointer to the same element in all cases (in your case, all pointers point to input). strdup will do it for you:

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

int main() {

    int nStructs = 3;

    Struct *structArray = calloc(nStructs, sizeof * structArray);

    char input[30];

    for (int i = 0; i < nStructs; i++) {
        structArray[i].id = i;
        sprintf(input, "name_%d", i);
        structArray[i].name = strdup(input);
        sprintf(input, "addr_%d", i);
        structArray[i].address = strdup(input);
    }

    for (int i = 0; i < nStructs; i++) {
        printf("\n[%04d]    ID: %d\n", structArray[i].id, structArray[i].id);
        printf("[%04d]  NAME:   %s\n", structArray[i].id, structArray[i].name);
        printf("[%04d]  ADDR:   %s\n", structArray[i].id, structArray[i].address);

        // Notice you now have to free those copies strings before your structs
        // go out of scope, or else you'll be leaking memory
        free(structArray[i].name);
        free(structArray[i].address);
    }

    return 0;
}

You might want to look up how exactly pointers work in C/C++.

CookiePLMonster
  • 195
  • 3
  • 11
  • `strdup` may not be readily available, but it's essentially a 2 liner and can be implemented easily. – Jabberwocky Jan 15 '18 at 14:31
  • Visual Studio was mentioned - I did a quick check before posting and it seems to be available for me. For cases where it's not available, you're right.. – CookiePLMonster Jan 15 '18 at 14:31
  • "You might want to look up how exactly pointers work in C/C++." Im new to C so I will do that. Now I understand more of how the memory works with calloc and the structs with strings. Ill read some more about pointers. Thanks for the good answer. This helped me a lot :) –  Jan 15 '18 at 14:33
0

You need to allocate memory for all of the strings in your struct with malloc or change the definitions of the struct to:

typedef struct {
    unsigned int id;
    char name[100];
    char address[100];
} Struct;

Currently all of the char pointers in your struct point to input, so as input changes the value of all the strings in your struct change.

To make your code work without changing the struct definitions, do something similar to the following:

for(int i = 0; i < nStructs; i++)
{
    structArray[i].name = malloc(100);
    structArray[i].address = malloc(100);
}

You can does this in the same for loop that you have, and allocate only as much memory that you need for each string. Be sure to check if malloc() returns NULL.

afic
  • 500
  • 4
  • 13
  • I dont want to change the struct. How do i allocate the memory for the strings? Isn't the memory allocated with the calloc() function in my code? –  Jan 15 '18 at 14:23
  • `structArray[i].name = input;` -> `structArray[i].name = malloc(strlen(input) + 1; strcpy(structArray[i].name, input);` – Jabberwocky Jan 15 '18 at 14:26
  • @MichaelWalz Thanks! That worked (with an extra ')' ). –  Jan 15 '18 at 14:29
  • 1
    Yep, be sure to check for the return of malloc() for NULL too. – afic Jan 15 '18 at 14:30
  • 2
    Don't forget to free this newly allocated memory or else you'll be leaking memory. – CookiePLMonster Jan 15 '18 at 14:30
  • 2
    `structArray[i].name = malloc(100)` is very, very ugly. Why waste memory if the strings are shorter than 100? And it the strings are longer than 100 you're out of luck. – Jabberwocky Jan 15 '18 at 14:34
  • To be fair, this code will already fail miserably if strings are over 30 characters - so a malloc of 100 will "never" be too small - as if it is, your program is already busted. – CookiePLMonster Jan 15 '18 at 14:36