【问题标题】:Dynamically allocate an array of dynamically allocated structures?动态分配一组动态分配的结构?
【发布时间】:2016-05-22 19:37:33
【问题描述】:

我正在尝试创建一个程序来读取文件并创建一个结构数组,其中填充了文件中的数据。问题是当我运行它时,我有时会得到一个“调试断言失败!”错误表达式:_CtrlsValidHeapPointer(block),有时不会有问题,有时Visual Studio调试器会这样说: ConsoleApplication6.exe 中 0x775C1B45 (ntdll.dll) 处未处理的异常:0xC0000005:访问冲突读取位置 0x0040D510。

这是我的代码:

#include <stdio.h>
#define SIZE 256

struct Record {
char * firstName;
char * lastName;
char * address;
char * city;
char * state;
int * zipCode;
int * phoneNumber;
};

void initializeRecord(struct Record * list, char * lineOfText, int i);
void makeList(char * lineOfText, struct Record * list, int * psize);

int main(void) {
    char lineOfText[SIZE];
    int size = 0;
    int * psize = &size;
    struct Record * list = malloc(sizeof(struct Record));
    makeList(lineOfText, list, psize);
    free(list);
}

void makeList(char * lineOfText, struct Record * list, int * psize)
{
    FILE * fp = fopen("myfile.txt", "r");
    while (fgets(lineOfText, SIZE, fp)) {
        list = realloc(list, sizeof(struct Record) + (*psize)*sizeof(struct Record));
    initializeRecord(list, lineOfText, *psize);
    (*psize)++;
    }
fclose(fp);
}

void initializeRecord(struct Record * list, char * lineOfText, int i) {
char * newline = strchr(lineOfText, '\n');
if (newline)
    *newline = 0;

char * firstName = strtok(lineOfText, "\t");
list[i].firstName = malloc(strlen(firstName)+1);
strcpy(list[i].firstName, firstName);
list[i].firstName[strlen(firstName) + 1] = '\0';

char *lastName = strtok(NULL, "\t");
list[i].lastName = malloc(strlen(lastName)+1);
strcpy(list[i].lastName, lastName);
list[i].lastName[strlen(lastName) + 1] = '\0';

char *address = strtok(NULL, "\t");
list[i].address = malloc(strlen(address)+1);
strcpy(list[i].address, address);
list[i].address[strlen(address) + 1] = '\0';

char *city = strtok(NULL, "\t");
list[i].city = malloc(strlen(city)+1);
strcpy(list[i].city, city);
list[i].city[strlen(address) + 1] = '\0';

char *state = strtok(NULL, "\t");
list[i].state = malloc(strlen(state)+1);
strcpy(list[i].state, state);
list[i].state[strlen(address) + 1] = '\0';

int *zipCode = strtok(NULL, "\t");
list[i].zipCode = malloc(strlen(zipCode)*sizeof(int)+1);
strcpy(list[i].zipCode, zipCode);
list[i].zipCode[strlen(zipCode)] = '\0';

int *phoneNumber = strtok(NULL, "\t");
list[i].phoneNumber = malloc(strlen(phoneNumber)*sizeof(int)+1);
strcpy(list[i].phoneNumber, phoneNumber);
list[i].phoneNumber[strlen(phoneNumber)] = '\0';

}

这是一个示例文件(除了中间不应该有全新的行,每个元素之间应该有制表符,我只是看不到如何在 StackOverflow 上格式化):

玛丽·琼斯 6201 Wioewjife Ave DOHfeo Hills AZ 93321 2465551234

比利鲍勃 7290 DIowhoefh St Uwopufeoi NY 23311 2345552393

约翰·琼斯 1234 EWOHFklfsh St WEDhofehif CA 98304 2345551238

马克·乔 2398 Yeiofejp Blvd Hdeefoidjs MT 13210 4355553973

我的猜测是我的问题是,当我为数组分配空间时,我没有分配足够的空间,因为 sizeof(struct Record) 可能不足以存储我要复制的内容,但我不知道如何创造足够的空间。

【问题讨论】:

  • 您为strcpy 包含的'\0' 终止符分配了足够的内存,但随后list[i].firstName[strlen(firstName) + 1] = '\0'; 不必要地写入了另一个终止符,超出了数组边界。
  • 将 API void makeList(char * lineOfText, struct Record * list, int * psize); 更改为 void makeList(char * lineOfText, struct Record ** list, int * psize);
  • 是时候结交使用调试器的朋友了。如果不能舒适地调试,就无法完成 C 编程。由于这里的控制流很简单,因此请专注于数据,我相信您会很快看到您的问题。
  • 另外,不要在每个函数中多次使用strlen(),除非在函数执行期间字符串的长度发生了变化。实际上,在您的情况下,最好 1. 使用strdup()。 2. 如果您使用的环境没有strdup() 或等效项,请编写一个简单的strdup() 函数以避免重复。
  • @George 请不要编辑代码中的错误。想想看——你最终会没有问题要回答,读者也不知道这些 cmets 和答案是关于什么的。

标签: c arrays pointers memory malloc


【解决方案1】:

您的代码中最大的错误是假设。你不能假设程序中的东西,特别是关于数据的东西。使用strtok() 时,您必须检查NULL 的返回值,这表明没有为请求的分隔符找到令牌。

使用strtok() 也不是标记字符串的首选方法,因为它不是可重入函数,在简单的情况下没问题,但如果它变得像解析 2 个字符串一样复杂,那么它就不起作用了。

使用malloc() 时,请始终确认分配成功。在一个简单的程序中,它不会引起大问题,但在数据敏感的程序中,您可能会破坏重要数据。这有很多原因,例如由于分段错误而丢失记录或写入损坏的数据,因为取消引用 NULL 指针并不能保证分段错误1。你应该避免。在无法关闭网络服务器的程序中,您必须确保检查所有内容是否有错误。所以作为一个好习惯总是检查错误

您的代码也是违反DRY Principle 的一个很好的例子,请尝试清理它,以便错误发生在一个位置,然后它们会影响到所有地方的程序,但您只能在一个位置修复它们。

还有一件事,因为 *alloc() 函数在失败时返回 NULL

list = realloc(list, ...);

可能是个问题,因为您会用NULL 覆盖list,并且在调用realloc() 之前也无法访问它所指向的数据。始终使用辅助变量然后realloc(),测试是否成功,然后覆盖旧指针。

即使 *alloc() 函数失败的可能性很小,但这也不是不可能的,一个健壮的程序应该认为它会发生,因为它确实发生了。


1这几乎肯定会发生,但根据标准,它应该是未定义的行为,所以无论如何都不能保证给定的行为

【讨论】:

  • OP 确实做了一些不安全的假设,但我想说最大的错误是@BLUEPIXY 在他的评论中提到的那个:realloc() 决不能确定就地扩展分配.如果它在 OP 的 makeList() 函数中没有这样做,则旧指针无效,但新指针不会返回给函数的调用者。
【解决方案2】:

您告诉initializeRecord() 您正在发送单个记录,然后您尝试将其作为记录数组进行访问。这就是导致您访问违规的原因。

【讨论】:

  • 我不明白你的意思,特别是在看了代码之后。
  • 首先他创建了一个记录指针。用“struct Record * list”正确表示。然后它正在重新分配,以便“列出”一个“记录”指针数组。但是数组仍然表示为单个指针。稍后,指向第一个 Record 的指针被传递给一个方法,我们被要求在它的分配之外访问该指针,将其用作数组。
  • 他传递指针和一个整数来指示要编辑的元素。没错。
  • 您似乎无法区分指针和数组。在 OP 的代码中,从不list 视为指针数组,甚至不是指向指针数组的指针。相反,它始终被视为指向struct Record 数组的第一个元素的指针。最初,可以认为是指向一个单元素数组;稍后,在一个多元素数组中
猜你喜欢
  • 2020-03-06
  • 2019-07-19
  • 1970-01-01
  • 2021-10-30
  • 2017-03-30
  • 1970-01-01
  • 2012-03-25
相关资源
最近更新 更多