Home > database >  How do i allocate memory for a new string in a C Multiarray
How do i allocate memory for a new string in a C Multiarray

Time:04-25

I am trying to find a way to create a dynamically allocated array of C strings. So far I have come with the following code that allows me to initialize an array of strings and change the value of an already existing index.

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

void replace_index(char *array[], int index, char *value) {
    array[index] = malloc(strlen(value)   1);
    memmove(array[index], value, strlen(value)   1);
}

int main(int argc, const char * argv[]) {
    char *strs[] = {"help", "me", "learn", "dynamic", "strings"};
    replace_index(strs, 2, "new_value");

    // The above code works fine, but I can not use it to add a value
    // beyond index 4.

    // The following line will not add the string to index 5.
    replace_index(strs, 5, "second_value");
}

The function replace_index will work to change the value of a string already include in the initializer, but will not work to add strings beyond the maximum index in the initializer. Is there a way to allocate more memory and add a new index?

CodePudding user response:

First off, if you want to do serious string manipulation it would be so much easier to use almost any other language or to get a library to do it for you.

Anyway, onto the answer.

The reason replace_index(strs, 5, "second_value"); doesn't work in your code is because 5 is out of bounds-- the function would write to memory unassociated with strs. That wasn't your question, but that's something important to know if you didn't. Instead, it looks like you want to append a string. The following code should do the trick.

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

typedef struct {
    char **content;
    int len;
} string_array;

void free_string_array(string_array *s) {
    for (int i = 0; i < s->len; i  ) {
        free(s->content[i]);
    }
    free(s->content);
    free(s);
}

int append_string(string_array *s, char *value) {
    value = strdup(value);
    if (!value) {
        return -1;
    }
    s->len  ;
    char **resized = realloc(s->content, sizeof(char *)*s->len);
    if (!resized) {
        s->len--;
        free(value);
        return -1;
    }
    resized[s->len-1] = value;
    s->content = resized;
    return 0;
}

string_array* new_string_array(char *init[]) {
    string_array *s = calloc(1, sizeof(string_array));
    if (!s || !init) {
        return s;
    }
    while (*init) {
        if (append_string(s, *init)) {
            free_string_array(s);
            return NULL;
        }
        init  ;
    }
    return s;
}

// Note: It's up to the caller to free what was in s->content[index]
int replace_index(string_array *s, int index, char *value) {
    value = strdup(value);
    if (!value) {
        return -1;
    }
    s->content[index] = value;
    return 0;
}

int main() {
    string_array *s = new_string_array((char *[]) {"help", "me", "learn", "dynamic", "strings", NULL});
    if (!s) {
        printf("out of memory\n");
        exit(1);
    }
    
    free(s->content[2]);
    // Note: No error checking for the following two calls
    replace_index(s, 2, "new_value");
    append_string(s, "second value");
    
    for (int i = 0; i < s->len; i  ) {
        printf("%s\n", s->content[i]);
    }
    free_string_array(s);
    
    return 0;
}

Also, you don't have to keep the char ** and int in a struct together but it's much nicer if you do.

If you don't want to use this code, the key takeaway is that the array of strings (char ** if you prefer) must be dynamically allocated. Meaning, you would need to use malloc() or similar to get the memory you need, and you would use realloc() to get more (or less). Don't forget to free() what you get when you're done using it.

My example uses strdup() to make copies of char *s so that you can always change them if you wish. If you have no intention of doing so it might be easier to remove the strdup()ing parts and also the free()ing of them.

CodePudding user response:

char *strs[] = {"help", "me", "learn", "dynamic", "strings"};

This declares strs as an array of pointer to char and initializes it with 5 elements, thus the implied [] is [5]. A more restrictive const char *strs[] would be more appropriate if one were not intending to modify the strings.

char strs[][32] = {"help", "me", "learn", "dynamic", "strings"};

This declares strs as an array of array 32 of char which is initialized with 5 elements. The 5 elements are zero-filled beyond the strings. One can modify this up to 32 characters, but not add more.

struct str_array { const char **data; size_t size, capacity; };

I think you are asking for a dynamic array of const char *. Language-level support of dynamic arrays is not in the standard C run-time; one must write one's own. Which is entirely possible.

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

struct str_array { const char **data; size_t size, capacity; };
static int str_array_reserve(struct str_array *const a, const size_t min) {
    size_t c0;
    const char **data;
    const size_t max_size = ~(size_t)0 / sizeof *a->data;
    if(a->data) {
        if(min <= a->capacity) return 1;
        c0 = a->capacity < 7 ? 7 : a->capacity;
    } else {
        if(!min) return 1;
        c0 = 7;
    }
    if(min > max_size) return errno = ERANGE, 0;
    /* `c_n = a1.625^n`, approximation golden ratio `\phi ~ 1.618`. */
    while(c0 < min) {
        size_t c1 = c0   (c0 >> 1)   (c0 >> 3);
        if(c0 >= c1) { c0 = max_size; break; } /* Unlikely. */
        c0 = c1;
    }
    if(!(data = realloc(a->data, sizeof *a->data * c0)))
        { if(!errno) errno = ERANGE; return 0; }
    a->data = data, a->capacity = c0;
    return 1;
}
static const char **str_array_buffer(struct str_array *const a,
    const size_t n) {
    if(a->size > ~(size_t)0 - n) { errno = ERANGE; return 0; }
    return str_array_reserve(a, a->size   n)
        && a->data ? a->data   a->size : 0;
}
static const char **str_array_append(struct str_array *const a,
    const size_t n) {
    const char **b;
    if(!(b = str_array_buffer(a, n))) return 0;
    return a->size  = n, b;
}
static const char **str_array_new(struct str_array *const a)
    { return str_array_append(a, 1); }
static struct str_array str_array(void)
    { struct str_array a; a.data = 0, a.capacity = a.size = 0; return a; }
static void str_array_(struct str_array *const a)
    { if(a) free(a->data), *a = str_array(); }

int main(void) {
    struct str_array strs = str_array();
    const char **s;
    size_t i;
    int success = EXIT_FAILURE;

    if(!(s = str_array_append(&strs, 5))) goto catch;
    s[0] = "help";
    s[1] = "me";
    s[2] = "learn";
    s[3] = "dynamic";
    s[4] = "strings";
    strs.data[2] = "new_value";
    if(!(s = str_array_new(&strs))) goto catch;
    s[0] = "second_value";
    for(i = 0; i < strs.size; i  ) printf("->%s\n", strs.data[i]);
    { success = EXIT_SUCCESS; goto finally; }
catch:
    perror("strings");
finally:
    str_array_(&strs);
    return success;
}

CodePudding user response:

but will not work to add strings beyond the maximum index in the initializer

To do that, you need the pointer array to be dynamic as well. To create a dynamic array of strings is one of the very few places where using a pointer-to-pointer to emulate 2D arrays is justified:

size_t n = 5;
char** str_array = malloc(5 * sizeof *str_array);
...
size_t size = strlen(some_string) 1;
str_array[i] = malloc(size);
memcpy(str_array[i], some_string, size);

You have to keep track of the used size n manually and realloc more room in str_array when you run out of it. realloc guarantees that previous values are preserved.

This is very flexible but that comes at the cost of fragmented allocation, which is relatively slow. Had you used fixed-size 2D arrays, the code would perform much faster but then you can't resize them.

Note that I used memcpy, not memmove - the former is what you should normally use, since it's the fastest. memmove is for specialized scenarios where you suspect that the two arrays being copied may overlap.

As a side-note, the strlen malloc memcpy can be replaced with strdup, which is currently a non-standard function (but widely supported). It seems likely that strdup will become standard in the upcoming C23 version of C, so using it will become recommended practice.

  • Related