【问题标题】:How to create a linked list from a binary input file? With first few bytes being an int and the next few a char and so on如何从二进制输入文件创建链表?前几个字节是 int,接下来的几个字节是 char,依此类推
【发布时间】:2019-08-14 02:10:31
【问题描述】:

我想从输入的二进制文件创建一个链表。第一个 sizeof(int) 字节是一个 int,下一个 sizeof(char) 字节是一个 char,它一直这样。 我想要做的是从此文件创建一个链表,其中链表中的每个节点都包含一个字符和一个包含此 int 值的树节点。

在从这个文件中创建一个链接列表时,我被卡住了。如果它只是一个带有整数且没有二进制文件和字符的常规文件,我会使用 fscanf 读取文件并将其内容存储到数组中,然后我会遍历数组并制作节点。但是,当文件中存在这些字符时,我感到很困惑。谁能帮助我并告诉我是否有创建链接列表的方法?

编辑 ->

ListNode *head = malloc(sizeof(ListNode)*sizeoffile);

//how do i find the size of the file.
//if it was a file with just integers, I would have done something like this
// int value;
// int count = 0;
//while(fscanf(fptr, "%d", &value)==1)
//{
//  count++;
//}
//But now that there is chars, I am really confused how I would determine  
//the size of the file.

while(!feof(fptr))
{
  fread(head, sizeof(int)+sizeof(char), 1, fptr);
}

I know this is not right. ^

【问题讨论】:

  • 从技术上讲,您的问题的答案是“是”(有一种创建链接列表的方法)。为什么需要先创建数组?如果可以遍历数组,就可以遍历输入文件。你到底卡在哪里了?
  • 如果你想要一个答案,你需要真正澄清这个问题并表现出一些努力。让我们看看,您可以开始编辑您的问题并向我们展示您从二进制文件中读取一个整数和一个字符的代码。在此之前向我们展示此类文件的十六进制转储。
  • @melpomene 我想遍历输入文件并将其放入链表中。我很困惑,因为当 fscanf 是链表时,我不知道如何使用它。我会在参数中使用 %d 还是 %c ?我不知道如何将这两件事都放在一个节点中。我很困惑,因为该文件是二进制文件。如果我没有很好地解释我的问题,我很抱歉。
  • @CostantinoGrana 我编辑了帖子以添加代码。我没有添加代码的原因是因为我一开始不知道如何编写代码,而且我对如何编写代码感到困惑。我添加了一些代码以使我的问题更清楚。如果您能提供帮助,那就太好了,如果我的问题不清楚,我深表歉意,我是初学者,我真的不知道如何解决这个问题。

标签: c file linked-list


【解决方案1】:

第 1 步:假设来自外部源(例如来自文件)的所有数据都可能存在恶意和/或损坏和/或来自不同的计算机(具有不同的 sizeof(int) 和不同的字节序)。

第 2 步:正确定义文件格式(考虑第 1 步)。例如。也许它应该是 123 到 123456 范围内的一个值,它以小端顺序存储为 4 个连续字节(它不应该是 int);也许它是一个包含 ASCII 字符的字节(它绝不应该是“编译器使用char 感觉的随机字符集”)。

第 3 步:编写一些代码以将文件中的数据加载到字节数组中。如果文件预计很小,如果缓冲区不够大,您可以使用realloc() 增加缓冲区的大小(但请确保有“最大文件大小”,以便恶意攻击者无法欺骗您消耗所有可用的 RAM 并由于“内存不足”而崩溃)。如果文件预计会更大;查看mmap() 之类的函数。或者,您可以使用“读取文件的下一部分;解析文件的下一部分”循环来回收固定大小的缓冲区。

第 4 步:编写代码来解析“字节数组”数据,并检查它是否以所有可能的方式实际符合文件格式规范。例如,unsigned long value = buffer[0] + (buffer[1] << 8) + (buffer[2] << 16) + (buffer[3] << 24)if( (value < 123) || (value > 123456) ) { // Data is malformed

第 5 步:一旦您解析了数据(并编写了代码来以适当的方式处理每个可能的错误条件,并且知道它必须是有效数据这一事实),您就可以将数据存储在一个结构中并将该结构添加到链表中。例如

    // Parse and check it

    if(bufferSize < position + 5) {
        return "File ends in the middle of a record";
    }
    unsigned long value = buffer[position] + (buffer[position+1] << 8) + (buffer[position+2] << 16) + (buffer[position+3] << 24);
    if( (value < 123) || (value > 123456) ) {
        return "Data was malformed (integer out of range)";
     }

    if( (buffer[position+4] < 0x20) || (buffer[position+4] >= 0x7F) ) {
        return "Data was malformed (character not printable ASCII)";
    }

    // Create a structure

    myStructureType * myStruct = malloc(sizeof(myStructureType));
    if(myStruct == NULL) {
        return "Failed to allocate memory for structure";
    }
    myStruct->value = value;
    myStruct->character = buffer[position+4];
    position += 5;

    // Add structure to singly linked list

    myStruct->next = NULL;
    if(listFirst == NULL) {
       listFirst =  myStruct;
    } else {
       listLast->next =  myStruct;
    }
    listLast =  myStruct;

【讨论】:

  • 这是一个很好的综合答案。 @noobie,如果其中一些目前似乎在您的脑海中(例如,字节序和不同的sizeof(int)),那么您可能希望就此提出后续问题。这里的简短故事是,您不能真正可靠地将 C 结构的内容推入文件中,然后再将它们吸回。在某些情况下你可以这样做,但你不应该从那个方向学习;按照@Brendan 在此处的建议为您的文件定义二进制格式,然后将其安全地处理到您的内存结构中。这种模式会很好地为您服务。
【解决方案2】:

好的,所以我建议您忘记链表。只要坚持第一个问题:从二进制文件中读取数据。

问题的文本不清楚对象的大小,所以我们假设它说:“有一个二进制文件包含由一个 32 位整数(小端序)和一个代表一个 8 位数字组成的小部件ASCII 字符。将所有小部件转储到 stdout 每行一个,表示以 10 为底的整数,后跟一个空格,然后是字符“。

假设您的 int 是 32 位小端序,而您的 char 是有符号字节,即假设您使用的是世界上 99.9% 的机器之一。

现在您必须阅读小部件,即一个 int 和一个 char。阅读时通常需要选择两个函数:fscanffread。第一个从格式化供人类阅读的数据中读取,而第二个从文件中读取字节。你现在需要哪一个?第二个,所以我们需要使用它。

在你写的代码中

while (!feof(fptr))

这是always wrong。读取文件的唯一正确方法是:

while (1) {
    // Read
    // Check
    // Use
}

然后你可以找到一种方法来读取和检查while条件,但相信我:第一次这样写。

所以让我们填充上面的模板。要检查 fread 是否成功,您需要检查它是否返回了您要求的元素数量。

while (1) {
    int i;
    char c;
    // Read
    int ok1 = fread(&i, 4, 1, fptr);
    int ok2 = fread(&c, 1, 1, fptr);
    // Check
    if (ok1 != 1 || ok2 != 1)
        break;
    // Use
    printf("%d %c\n", i, c);
}

当然你可以在 while 条件下打包,但我看不出有什么理由。

现在我将使用您的输入和一个好的调试器对此进行测试,并检查文件中的所有数据是否都被打印出来。如果一切正常,您可以继续解决剩下的问题,即将这些小部件放在一个链表中。

这里我假设你还没有学习structs。如果不是这种情况,您可以与他们合作:

struct widget {
    int i;
    char c;
};

[...]

while (1) {
    struct widget w;
    // Read
    int ok1 = fread(&w.i, 4, 1, fptr);
    int ok2 = fread(&w.c, 1, 1, fptr);
    // Check
    if (ok1 != 1 || ok2 != 1)
        break;
    // Use
    printf("%d %c\n", w.i, w.c);
}

不要被小部件与文件中的数据结构相同这一事实所迷惑。你不能相信

fread(&w, 5, 1, fptr); // No! Don't do this

将正确读取您的数据。在制作结构体时,编译器可以在字段之间放置所需的所有空间,所以如果sizeof(widget) 返回 8,我不会感到惊讶。

免责声明:我直接在浏览器上写了代码,没有检查!

【讨论】:

    【解决方案3】:

    我认为你有点太沉迷于一些不是真正根本问题的事情。如果您需要从文件创建链表,您可以使用fscanf()fread() 或任何您喜欢的方式将文件读入缓冲区并根据需要操作该缓冲区。解析整数数组(从文件中读取)的相同逻辑可以应用于解析二进制文件中的字符串缓冲区(你说二进制文件与sizeof(int)sizeof(char) 连续所以我要假设你的意思是它可以被读入缓冲区)

    你说

    "如果它只是一个带有整数且没有二进制和字符的常规文件,我 会使用fscanf 读取文件并将其内容存储到 一个数组,然后我会遍历数组并制作 节点”

    您可以使用相同的逻辑来遍历字符串或字符串列表(但是您决定解析缓冲区)来创建节点。这就是 C 中的数据结构或结构的美妙之处。

    【讨论】:

    • 感谢您的回复,非常感谢。那么它会不会像 while(!feof(fptr)){ fread(some_linked_list_pointer, sizeof(int)+sizeof(char), 1, fptr); }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多