C语言中的动态大小数组

3
我正在尝试使用http://c.learncodethehardway.org/学习C语言,但我卡在了第18章的一道额外练习题上(http://c.learncodethehardway.org/book/learn-c-the-hard-waych18.html),希望有人能帮助我。

我遇到的具体问题是有几个这样定义的结构体:

#define MAX_ROWS = 500;
#define MAX_DATA = 512;

struct Address {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

struct Database {
    struct Address rows[MAX_ROWS];       
};     

struct Connection {
    FILE *file;
    struct Database *db;
}; 

挑战在于重新设计 rows,使其大小不依赖于常数。因此,在我编写的 Database_create 方法中,我尝试使用以下代码初始化 rows
conn->db->rows = (struct Address*) malloc(max_rows * sizeof(struct Address));

其中conn->db指向一个Database实例,max_rows是传递给函数的整数。我还改变了Database结构体的定义:

struct Database{
    struct Address* rows;
}

这段代码似乎运行良好,但如果我尝试访问rows的任何成员,我会得到一个分段错误,我相信这意味着我正在尝试访问未被使用的内存位。

我已经花了好几个小时在这个问题上,我确定我不可能太远了,但我真的很感激任何指导,帮我找到正确的方向。


编辑:在使用Valgrind运行后,我想添加更多的细节,它会抛出错误:

==11972== Invalid read of size 4
==11972==    at 0x100001578: Database_set (ex18.c:107)
==11972==    by 0x100001A2F: main (ex18.c:175)
==11972==  Address 0x7febac00140c is not stack'd, malloc'd or (recently) free'd

它指向的代码行是:
struct Address *addr = &conn->db->rows[id];
if(addr->set) die("Already set, delete it first");   

第107行是if(addr->set),我推测这意味着它试图读取一些无法读取的内容。


如果您正在使用符合c99标准的编译器,它们确实允许变长数组。 - Alok Save
我总是对 &-> 之间的运算符优先级感到模糊,所以当有疑问时,请用括号括起来。为了确保,你尝试过 struct Address *addr = &(conn->db->rows[id]); 吗? - Dan F
看了那个问题的被接受的答案,似乎你只是不想在没有包含 stdlib.h 的情况下使用 malloc。 - Eric Finn
id是什么?你确定它在0和max_rows之间吗? - Dan F
@pogo 我的意思是要求 conn 的定义,而不是 conn 的类型,虽然这也有帮助。只是我注意到你在一个地方使用了 conn->db->rows,而在另一个地方使用了 &conn->db->rows。这两个 conn 是相同的吗?如果你在使用 &connconn 已经是一个指针,那么这可能解释了错误。 - Eric Finn
显示剩余8条评论
3个回答

2
您需要使用sizeof(struct Address)而不是sizeof(struct Address*)sizeof(struct Address*)返回的大小可能为4(完全取决于目标平台),而Address结构体的实际大小更接近1040(假设每个字符占1个字节,每个整数占4个字节)。

那似乎没有解决问题,我刚刚更新了原始帖子以反映这一点,但我还添加了当我通过Valgrind运行它时得到的错误,希望能更多地阐明发生了什么。 - pogo

1
编辑:看起来(根据您的最新编辑),您实际上已经正确地完成了这一部分。

您实际上没有为地址结构分配足够的大小。您需要像这样做:

struct Database{
    struct Address** rows;
}
//create array of pointers to Address structures
// (each element has size of the pointer)
conn->db->rows = (struct Address**) malloc(max_rows * sizeof(struct Address*));
for(int i=0; i < max_rows; i++) {
    conn->db->rows[i] = (struct Address*) malloc(sizeof(struct Address));
}

或者这个:

struct Database{
    struct Address* rows;
}
//create array of Address structures
// (each element has size of the structure)
conn->db->rows = (struct Address*) malloc(max_rows * sizeof(struct Address));

在某些情况下,你可能想要使用 sizeof(struct Address*) 的好答案突出了原因。 - Dan F

1

当你在加载时

void Database_load(struct Connection *conn)
{
        int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
        if(rc != 1) die("Failed to load database");
}

或者编写数据库,

void Database_write(struct Connection *conn)
{
        rewind(conn->file);

        int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
        if(rc != 1) die("Failed to write database");

        rc = fflush(conn->file);
        if(rc == -1) die("Cannot flush database.");
}

你不是在读写内容(struct Address),而是在读写指向内存位置的指针。当读取先前编写的数据库时,该指针不指向任何特定的位置,它是一个野指针。然后,如果尝试对其进行解引用,则很可能会导致分段错误,如果偶然没有发生分段错误,则会得到无意义的伪数据。

如果您更改struct Database,使rows成为struct Address*,则需要保持项目计数,并更改读写代码以处理由rows指向的数据。首先写入您拥有的项目数量,然后写入其余部分(max_datamax_rows和项目); 在阅读时,读取您拥有的项目数量,为它们分配空间,读取max_datamax_rows以及项目。


我认为这绝对是发生的事情,我注意到当我在示例中运行代码时,它会创建一个大数据文件,但当我运行我的代码时,它非常小,并且在尝试从中读取时发生分段错误。您知道有哪些文章/示例代码可以让我了解如何确保写入文件时足够大吗? - pogo
文件大小不是问题,它只是问题的症状。在结构体中使用char name[MAX_DATA];email同理),数据数组是结构体的一部分。因此,在写入结构体时,名称和电子邮件也被写入了(加上一些垃圾,因为名称和电子邮件较短)。当您在结构体中使用char *name; char *email;时,结构体仅包含数据的地址。因此,在写入结构体时,您不会写入数据,只会写入内存中的地址(在程序运行期间)。这对您没有任何帮助,因为当您读取数据时,该地址可能已经无效。 - Daniel Fischer
如果你写了它,它可能会驻留在不同的位置。你需要做的是:1.写入idset,2.写入名称的大小(长度)和名称本身,3.写入电子邮件的大小(长度)和电子邮件本身。当读取时,您需要读取idset,名称的大小,分配适当长度的缓冲区,将名称读入该缓冲区,将email字段设置为缓冲区的地址,读取电子邮件的大小,分配另一个缓冲区,将电子邮件读入其中并将email字段设置为缓冲区的地址。 - Daniel Fischer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接