【问题标题】:Dynamically sized arrays in CC中的动态大小数组
【发布时间】:2012-06-22 13:03:08
【问题描述】:

我正在尝试使用 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 指向数据库实例,max_rows 是传递给函数的 int。 我还将数据库结构更改为

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 的编译器,它们确实允许可变长度数组。
  • 作为一个小问题,您不想强制转换 malloc 的返回值(请参阅:stackoverflow.com/questions/1565496/…)。
  • 我对 &-> 之间的运算符优先级总是很模糊,所以当有疑问时,用括号括起来。只是为了确定,你试过struct Address *addr = &(conn->db->rows[id]);吗?
  • @Scroog1 阅读该问题的已接受答案,看起来您只是不想在不包含 stdlib.h 的情况下使用 malloc。
  • struct Address *addr = &conn->db->rows[id]; 可以看看conn的定义吗?

标签: c pointers


【解决方案1】:

你想要sizeof(struct Address) 而不是sizeof(struct Address*)

sizeof(struct Address*) 可能返回 4 的大小(不过完全取决于目标平台),而 Address 结构的实际大小更接近 1040(假设每个 char 1 个字节,每个 int 4 个)

【讨论】:

  • 这似乎并没有解决它,我刚刚更新了我的原始帖子以反映这一点,但我还添加了我通过 Valgrind 运行它时得到的错误,希望能摆脱对正在发生的事情有更多了解
【解决方案2】:

加载时

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 Addresses),而只是指向内存位置的指针。在读取以前编写的数据库时,该指针不指向任何特定的东西,它是一个野指针。然后当然尝试取消引用它很可能会导致分段错误,如果没有,您将得到无意义的伪数据。

如果您将struct Database 更改为rowsstruct Address*,您需要保留项目计数并更改您的读写代码以处理rows 指向的数据。先写你有多少物品,然后写剩下的(max_datamax_rows和物品);阅读时,阅读你有多少物品,为它们分配空间,阅读max_datamax_rows和物品。

【讨论】:

  • 我认为这绝对是正在发生的事情,我注意到当我运行示例中的代码时它会创建一个大数据文件,但是当我运行我的代码时它非常小并且尝试读取时会发生分段错误从那里回来。您是否知道我可以查看的任何文章/示例代码可以解释如何确保文件在写入时足够大?
  • 文件大小不是问题,它只是问题的症状。在结构中使用char name[MAX_DATA];email 相同)时,包含数据的数组是结构的一部分。因此,在编写结构时,名称和电子邮件也被写入(加上一些垃圾,因为名称和电子邮件更短)。当结构中有char *name; char *email; 时,结构只包含数据的地址。因此,当您 write 结构时,您不会写入数据,只写入它在内存中的地址(在此程序运行中)。这对你一点帮助都没有,因为当你读取数据时
  • ... 回到(如果你写过的话),它可能会驻留在不同的位置。你需要做的是1.写idset,2.写名字的大小(长度)和名字本身,3.写电子邮件的大小(长度)和电子邮件本身.而回读时,需要读取idset,名称的大小,分配适当长度的缓冲区,将名称读入该缓冲区,将email字段设置为缓冲区的地址,读取电子邮件的大小,分配另一个缓冲区,将电子邮件读入其中并将email字段设置为缓冲区的地址
【解决方案3】:

编辑:嗯,看起来(根据您的最新编辑),您实际上正在正确地完成这部分。

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

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*)
猜你喜欢
  • 2011-10-24
  • 1970-01-01
  • 2011-05-03
  • 2012-04-17
  • 2012-10-02
  • 2014-02-23
  • 2014-11-15
  • 2020-11-13
相关资源
最近更新 更多