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

时间:2017-01-18 08:04:46

标签: c arrays struct dynamic-allocation

我有这些结构,我想用它来实现一个地图

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的第一次调用中设置数据后,所有内容似乎都存储在正确的位置。

任何人都知道可以覆盖这些条目或者为什么不存储这些值?

3 个答案:

答案 0 :(得分:3)

问题在于:

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

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

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

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

答案 1 :(得分:1)

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

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

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

正确的代码如下所示

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

答案 2 :(得分: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;
}

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

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