如何将数据存储在动态结构数组中?

How to store data in a dynamic array of structs?

我有这些结构,我想用它们来实现映射

typedef struct {
    const char *name;
    int number;
}   Entry;

typedef struct {
    int available;
    int guard;
    Entry *entries;
} Map;

以及初始化和放入元素的代码:

Map *map_init() {
    Map *res = (Map *) malloc(sizeof(Map));

    res->available = 4;
    res->guard = 0;
    res->entries = (Entry *) malloc(4 * sizeof(Entry));

    return res;
}

int map_put(Map *map, const char *name, int nr) {
    Entry entry;
    int i = 0;

    for (i = 0; i < map->guard; ++i) {
        entry = map->entries[i];
        printf("entry (  x , %u) at %p (%p)\n", entry.number, &entry, entry.name);

        if (!strcmp(entry.name, name))        // Segmentation fault here
            return 0;
    }

    entry = map->entries[map->guard++];
    entry.name = name;
    entry.number = nr;

    printf("entry (%s, %u) at %p (%p)\n", entry.name, entry.number, &entry, entry.name);

    return 1;
}

当我运行我的主要方法

int main(int argc, char **argv) {
    printf("Initialising...\n");
    Map *map = map_init();

    printf("Putting...\n");
    map_put(map, "test", 2);
    map_put(map, "some", 1);

    // ...

    free(map->entries);
    free(map);
    return 0;
}

我得到输出

Initialising...
Putting...
entry (test, 2) at 0x7fff50b32a90 (0x10f0cdf77)
entry (  x , 0) at 0x7fff50b32a90 (0x5000000000000000)
Segmentation fault: 11

从中我可以得出分段错误是由于 entry.name 不再指向字符串(数字也丢失了,但这不会导致未经授权的内存访问)。在我第一次调用 map_put 设置数据后,一切似乎都存储在正确的位置。

有人知道这些条目可以在哪里被覆盖或者为什么不存储这些值吗?

问题是这样的:

entry = map->entries[map->guard++];

在这里,您将数组中的数据复制到entry结构实例中。然后您修改 entry 的数据并丢弃这些修改。数组中的(原始)结构数据仍未修改。

当您在下一次调用 map_put 时使用数组中未初始化的结构时,这当然会导致 未定义的行为

要么直接修改数组结构实例,单独增加map->guard。或者使 entry 成为 指针 并使其指向数组元素。

问题是 map_put 中的变量 entry 不是指针。它是一个结构。所以代码

entry = map->entries[map->guard++];
entry.name = name;
entry.number = nr;

map->entries[map->guard]的内容复制到entry中。然后从函数中更新 entry 和 return 中的字段。

正确的代码如下所示

int map_put(Map *map, const char *name, int nr) {
    Entry *entry;  // <-- entry is a pointer
    int i = 0;

    for (i = 0; i < map->guard; ++i) {
        entry = &map->entries[i];
        printf("entry (  x , %u) at %p (%p)\n", entry->number, (void *)entry, (void *)entry->name);

        if (!strcmp(entry->name, name))
            return 0;
    }

    entry = &map->entries[map->guard++];
    entry->name = name;
    entry->number = nr;

    printf("entry (%s, %u) at %p (%p)\n", entry->name, entry->number, (void *)entry, (void *)entry->name);

    return 1;
}

您在 map_put 中遇到了重大问题。您使用本地 Entry,其中您从地图中 复制 条目。但是当您稍后为本地副本赋值时,地图中的原始条目将保持不变。

因此,当您稍后尝试将新名称与现有条目进行比较时,您将其与未初始化的值进行比较,这就是未定义的行为。

您应该改用 Entry *

int map_put(Map *map, const char *name, int nr) {
    Entry *entry;
    int i = 0;

    for (i = 0; i < map->guard; ++i) {
        entry = map->entries + i;
        printf("entry (  x , %u) at %p (%p)\n", entry->number, entry, entry->name);

        if (!strcmp(entry->name, name))        // Segmentation fault here
            return 0;
    }

    entry = &map->entries[map->guard++];
    entry->name = name;
    entry->number = nr;

    printf("entry (%s, %u) at %p (%p)\n", entry->name, entry->number, entry, entry->name);

    return 1;
}

但这还不是全部。您只需将字符串的地址存储在名称中。在这个例子中很好,因为你实际上是在传递字符串常量。但是,如果您从标准输入或文件中读取字符串,缓冲区的内容将被每个新值覆盖。由于您只存储地址,因此所有条目都指向相同的值:最后一个。

恕我直言,您应该考虑使用 strdup 来存储字符串的副本 - 并在最后释放它们。顺便说一句,因为你有一个 init 函数来初始化你 Map,你应该构建一个清理函数,以便在一个地方释放所有必要的东西。