如何为 C 多数组中的新字符串分配内存?

How do I allocate memory for a new string in a C Multiarray?

我正在尝试找到一种创建动态分配的 C 字符串数组的方法。到目前为止,我已经有了以下代码,它允许我初始化一个字符串数组并更改一个已经存在的索引的值。

#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");
}

函数replace_index 将用于更改已包含在初始化程序中的字符串的值,但不能用于添加超出初始化程序中最大索引的字符串。有没有办法分配更多内存并添加新索引?

首先,如果您想进行严格的字符串操作,使用几乎任何其他语言或获取一个库来为您做这件事会容易得多。

无论如何,进入答案。

replace_index(strs, 5, "second_value"); 在您的代码中不起作用的原因是因为 5 超出范围 - 该函数将写入与 strs 无关的内存。那不是你的问题,但如果你不问的话,知道这一点很重要。相反,您似乎想要附加一个字符串。下面的代码应该可以解决问题。

#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;
}

此外,您 没有 char **int 放在一个结构中,但如果这样做会更好。

如果您不想使用此代码,关键要点是必须动态分配字符串数组(char **,如果您愿意)。意思是,您需要使用 malloc() 或类似的方法来获取所需的内存,并且您将使用 realloc() 来获取更多(或更少)的内存。不要忘记 free() 当您使用完后会得到什么。

我的示例使用 strdup() 来制作 char * 的副本,以便您随时可以根据需要更改它们。如果您无意这样做,删除 strdup()ing 部分及其 free()ing 部分可能更容易。

静态数组

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

这将 strs 声明为 array of pointer to char 并用 5 个元素对其进行初始化,因此隐含的 [][5]。如果不打算修改字符串,则更严格的 const char *strs[] 会更合适。

最大长度

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

这将 strs 声明为一个 array of array 32 of char,它用 5 个元素初始化。这 5 个元素在字符串之外 zero-filled。最多可以修改 32 个字符,但不能添加更多。

常量字符串的最大单例容量

static struct str_array { size_t size; const char *data[1024]; } strs;

这将 pre-allocate 启动时的最大容量并使用它来满足请求。其中,capacity为1024,但size可以是任意数字,不超过capacity。我做这个 static 的原因是通常要放置很多堆栈。没理由不能按要求做动态内存。

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

static struct { size_t size; const char *data[1024]; } strs;
static const size_t strs_capacity = sizeof strs.data / sizeof *strs.data;

/** Will reserve `n` pointers to strings. A null return indicates that the size
 is overflowed, and sets `errno`, otherwise it returns the first string. */
static const char **str_array_append(const size_t n) {
    const char **r;
    if(n > strs_capacity - strs.size) { errno = ERANGE; return 0; }
    r = strs.data + strs.size;
    strs.size += n;
    return r;
}

/** Will reserve one pointer to a string, null indicates the string buffer is
 overflowed. */
static const char **str_array_new(void) { return str_array_append(1); }

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

    if(!(s = str_array_append(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())) 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:
    return success;
}

动态数组

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

我认为您要求的是 const char * 的动态数组。 Language-level 标准不支持动态数组 C run-time;一个人必须自己写。这是完全可能的,但涉及更多。因为大小是可变的,所以它可能会更慢,但随着问题的增长,在极限内,以恒定的平均值。

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

/** A dynamic array of constant strings. */
struct str_array { const char **data; size_t size, capacity; };

/** Returns success allocating `min` elements of `a`. This is a dynamic array,
 with the capacity going up exponentially, suitable for amortized analysis. On
 resizing, any pointers in `a` may become stale. */
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 < 5 ? 5 : a->capacity;
    } else {
        if(!min) return 1;
        c0 = 5;
    }
    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;
}

/** Returns a pointer to the `n` buffered strings in `a`, that is,
 `a + [a.size, a.size + n)`, or null on error, (`errno` will be set.) */
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;
}

/** Makes any buffered strings in `a` and beyond if `n` is greater then the
 buffer, (containing uninitialized values) part of the size. A null on error
 will only be possible if the buffer is exhausted. */
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;
}

/** Returns a pointer to a string that has been buffered and created from `a`,
 or null on error. */
static const char **str_array_new(struct str_array *const a) {
    return str_array_append(a, 1);
}

/** Returns a string array that has been zeroed, with zero strings and idle,
 not taking up any dynamic memory. */
static struct str_array str_array(void) {
    struct str_array a;
    a.data = 0, a.capacity = a.size = 0;
    return a;
}

/** Erases `a`, if not null, and returns it to idle, not taking up dynamic
 memory. */
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;
}

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

为此,您还需要指针数组是动态的。创建动态字符串数组是为数不多的可以使用 pointer-to-pointer 模拟二维数组的地方之一:

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);

您必须手动跟踪已用大小 n,并且当您 运行 超出 str_arrayrealloc 有更多空间。 realloc 保证保留以前的值。

这非常灵活,但代价是分散分配,相对较慢。如果您使用 fixed-size 二维数组,代码会执行得更快,但您无法调整它们的大小。

请注意,我使用的是 memcpy,而不是 memmove - 前者是您通常应该使用的,因为它是最快的。 memmove 适用于您怀疑正在复制的两个数组可能重叠的特殊情况。

作为side-note,strlen+malloc+memcpy可以替换为strdup,目前是non-standard功能(但得到广泛支持)。 strdup 似乎有可能成为即将推出的 C23 版本 C 的标准,因此使用它会成为推荐的做法。