0

Trying to add another element to a struct array in C (Windows specific, using VS2019 Community). Works fine, until I try to assign the return value of realloc to the original array. The code in main (declarations and initialization, as well as calling code) is as follows:

// server.h
struct server {
    wchar_t* name;
    wchar_t ip_address[16];
    int port;
};

// main.c
static int nb_servers = 0;
static struct server* servers = NULL;

void add_server(wchar_t* name, wchar_t ip_address[16], wchar_t* port)
{
    struct server newserver;
    newserver.name = name;

    wcsncpy(newserver.ip_address, ip_address, 16);

    char* port_char = malloc(6);
    if (port_char == NULL) {
        exit(EXIT_FAILURE);
    }
    size_t i;
    wcstombs_s(&i, port_char, 6, port, _TRUNCATE);

    int port_int = 0;
    str2int(&port_int, port_char, 10);

    newserver.port = port_int;

    // add to servers
    nb_servers = server_add(&servers, &newserver, nb_servers);
}

Then in another file, this is where I try to add the new server to the list:

// server.c
int server_add(struct server** servers, struct server* myserver, int nb_servers)
{
    struct server* tmp = (struct server*) realloc(*servers, (nb_servers + 1) * sizeof(struct server));
    if (tmp == NULL) {
        exit(EXIT_FAILURE);
    }

    tmp[nb_servers].name = (wchar_t*) calloc(strlen(myserver->name), sizeof(wchar_t));
    if (tmp[nb_servers].name == NULL) {
        exit(EXIT_FAILURE);
    }
    wcsncpy(tmp[nb_servers].name, myserver->name, strlen(myserver->name));
    wcsncpy(tmp[nb_servers].ip_address, myserver->ip_address, 16);
    tmp[nb_servers].port = myserver->port;

    *servers = tmp; // this only copies the first value [0]
                    // also tried **servers = *tmp and other combinations, nothing seems to work.

    return ++nb_servers;
}

But only the first value is 'copied', or rather only servers[0] point to a valid object. However, tmp[0] to tmp[nb_servers - 1] are valid and contain the correct data. I'm using a similar reallocation mechanism to shrink the array in a remove_server method and that same reassignment works in that case.

Question: How to correctly add a struct item to an array of structs by dynamically reallocating memory?

evilmandarine
  • 4,241
  • 4
  • 17
  • 40
  • First, do you understand what [`wcsncpy`](https://en.cppreference.com/w/c/string/wide/wcsncpy) *really* does (particularly in the case when the length of data to be copied meets or exceeds the length provided for the target buffer? Unrelated, [don't cast return values from C memory allocation functions](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc?r=SearchResults&s=1|783.5490) – WhozCraig Oct 14 '19 at 17:50
  • @WhozCraig I _thought_ I did, the explanation in the reference is clear. Should I copy len-1 characters and null terminate the string myself instead? (as you can guess I am a total beginner in C). – evilmandarine Oct 14 '19 at 18:09
  • You should setup the dynamic allocation to include space for the terminator, then just use `wcscpy` Or.. if your implementation supports it, just use `wcsdup` (which does both steps for you). – WhozCraig Oct 14 '19 at 18:10
  • @WhozCraig I now do it like this: `wcsncpy(tmp[nb_servers].name, myserver->name, wcslen(myserver->name)-1); tmp[nb_servers].name[wcslen(myserver->name)] = '\0';` I also removed the return values casts from the alloc methods. This works but the original issue is still there: servers only contain first element of tmp. – evilmandarine Oct 14 '19 at 18:21
  • That's still wrong. Do what we said. Allocate length+1 for the name and just use `wcscpy`. As you have it now you're (a) under-copying your string, and (b) invoking undefined behavior by accessing memory one-char-past the allocated length. Assuming you get around to doing that correctly, how you are actually validating your add-operation is failing? That code isn't posted. – WhozCraig Oct 14 '19 at 18:32
  • @WhozCraig Ok, I did it: `wcscpy(tmp[nb_servers].name, myserver->name);` after allocating `wcslen(myserver->name)+1`. I am using the Immediate Window of VS2019 with a breakpoint on the `return` and tmp[0] and [1] are correct, while only server[0] is correct. server[1] is `0x00000000 `. – evilmandarine Oct 14 '19 at 21:12
  • @WhozCraig Man, in the caller, the Immediate window shows the correct values. But in the function, it does not, I don't know why. Your last comment put me in the correct direction. Sorry to have made you lose your time. – evilmandarine Oct 14 '19 at 21:21
  • There is no `servers[1]`. you're passing the address of a pointer (which is correct). Which means you have out argument to store a pointer (which you're doing with `*servers = tmp;` all does is save the new sequence base to the target you provided). If you wanted to see the item at slot 1 you would use `(*servers)[1]` (in that function context). in short, your `servers[1]` is asking for the second *pointer* is a sequence of pointers; that's not what you have. The caller side should look how you want it. I think you're just misunderstanding how a multi-indirection pointer works. – WhozCraig Oct 14 '19 at 21:23
  • @WhozCraig It's becoming more clear. Thank you for your help! – evilmandarine Oct 15 '19 at 08:26

0 Answers0