【问题标题】:fread(): Reading from a file (without alignment) results in skipping of bytesfread():从文件中读取(不对齐)会导致跳过字节
【发布时间】:2020-07-23 11:45:34
【问题描述】:

我有一个文件并使用 C 我想使用 fread()(来自 stdio.h)读取它的内容并将其写入结构的成员中。 (在我的例子中,开头有一个 2 字节的 int,后跟一个 4 字节的 int。) 但是在将文件内容正确写入结构的前两个字节变量后,它会跳过两个字节,然后继续执行第二个四字节变量。

为了演示,我创建了一个 16 字节的文件来读取。在十六进制中它看起来像这样(小端): 22 11 66 55 44 33 11 11 00 00 00 00 00 00 00 00

使用以下代码,我希望第一个变量twobytes0x1122,第二个变量fourbytes0x33445566。但它会打印:

twobytes: 0x1122 
fourbytes: 0x11113344

sizeof(FOO) = 8
&foo     : 0061FF14
&foo.two : 0061FF14
&foo.four: 0061FF18

跳过字节 3 和 4 (0x66 & 0x55)。代码:

#include <stdio.h>
#include <stdint.h>

int main(void) {

    FILE* file = fopen("216543110.txt", "r");
    if (file==NULL) { return 1; }

    typedef struct
    {
        uint16_t twobytes;
        uint32_t fourbytes;
    }__attribute__((__packed__)) // removing this attribute or just the underscores around packed does not change the outcome
    FOO;
    
    FOO foo;
    
    fread(&foo, sizeof(FOO), 1, file);
    
    printf("twobytes: 0x%x \n", foo.twobytes);
    printf("fourbytes: 0x%x \n\n", foo.fourbytes);

    printf("sizeof(FOO) = %d\n", sizeof(FOO));
    printf("&foo     : %p\n", &foo);
    printf("&foo.two : %p\n", &foo.twobytes);
    printf("&foo.four: %p\n", &foo.fourbytes);
    
    fclose(file);
    return 0;
}

使用具有两个相同大小整数的结构可以按预期工作。


所以:使用 fread() 写入不同大小的变量会导致跳过字节:

22 11 .. .. 44 33 11 11 ...

而不是

22 1166 55 44 33 ...


我知道字节对齐在此处发挥了作用,但这对字节的读取有何影响?如果 C 想要为结构添加填充,这对从文件中读取有何影响? 我不在乎 C 是否将结构成员存储为 22 11 .. .. 66 55 44 33 ... 或者 22 1166 55 44 33 ..., 我很困惑为什么它无法正确读取我的文件。

另外,我正在使用gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)

【问题讨论】:

  • 如果结构体 sizeof 是 8 个字节,那么它读取 8 个字节。
  • 您应该检查/打印sizeof(FOO) 的值,它在您的代码中似乎是 8 个字节,并在两个结构成员之间添加了两个“缺失”字节作为填充。我认为您需要将__attribute__((__packed__)) 应用于结构的个人成员
  • @Paul Tashkent,由于您询问的是非标准编译器扩展,因此您应该提及编译器及其版本
  • @PaulTashkent,没有理由认为fread() 会跳过任何字节。问题在于它将读取的字节放在哪里,以及它与structtwobytesfourbytes 成员之间的关系。如果您在这些成员中的任何一个中都看不到预期的字节,那么唯一可能的答案是它们进入了这两个成员之间的结构中的填充字节。为什么你的编译器接受 __attribute__((__packed__)) 却仍然用填充来布局结构是另一个问题。
  • This answer 也很有意思,推荐改用#pragma pack

标签: c struct byte fread struct-member-alignment


【解决方案1】:

从您的程序产生的输出来看,编译器似乎忽略了__attribute__(__packed__) 规范。

gcc online user's guide 记录了 __attribute__ ((__packed__)) 类型属性,并附有一个示例,其中该属性放置在定义的 { 之前。

此扩展是非标准的,因此不同的编译器或任何给定编译器的不同版本可能会根据放置选择以不同的方式处理它。如果您使用 gcc,移动属性应该可以解决问题。如果您使用不同的编译器,请查看文档以了解它的不同之处。

还要注意这些备注:

  • 文件应该以二进制模式打开,"rb"
  • 对于%d 转换说明符,sizeof(FOO) 参数应转换为 (int)
  • %p 的指针参数应转换为 (void *)
  • foo.twobytesfoo 具有相同的地址,这是 C 标准规定的,&amp;foo.fourbytes 位于 4 个字节之外,这意味着 foo.fourbytes 是对齐的,两个成员之间有 2 个填充字节。

尝试以这种方式修改您的代码:

#include <stdio.h>
#include <stdint.h>

int main(void) {
    FILE *file = fopen("216543110.txt", "rb");
    if (file == NULL) {
        return 1;
    }

    typedef struct __attribute__((__packed__)) {
        uint16_t twobytes;
        uint32_t fourbytes;
    } FOO;
    
    FOO foo;
    
    if (fread(&foo, sizeof(FOO), 1, file) == 1) {
        printf("twobytes : 0x%x\n", foo.twobytes);
        printf("fourbytes: 0x%x\n\n", foo.fourbytes);

        printf("sizeof(FOO) = %d\n", (int)sizeof(FOO));
        printf("&foo     : %p\n", (void *)&foo);
        printf("&foo.two : %p\n", (void *)&foo.twobytes);
        printf("&foo.four: %p\n", (void *)&foo.fourbytes);
    }
    fclose(file);
    return 0;
}

【讨论】:

  • 虽然你的回答很好而且我赞成,但我想指出主要问题是不同的:__attribute__((__packed__)) 对带有__attribute__((ms_struct)) 的结构没有影响,这是面向 Microsoft Windows x86 平台时的默认值。有关更多信息,请参阅我的答案。
  • @AndreasWenzel:好点!抱歉,我错过了您最新的 cmets... 几十年来已经浪费了很多时间来处理 MS 遗留平台的怪癖和陷阱... 比这家公司的市场价值还要多的钱付诸东流(!)
【解决方案2】:

在 GCC 上,针对 x86 平台时,

__attribute__((__packed__))

仅适用于结构体

__attribute__((gcc_struct)).

但是,当面向 Microsoft Windows 平台时,结构的默认属性是

__attribute__((ms_struct)).

因此,我看到了三种方法来完成你想要的:

  1. 使用编译器命令行选项-mno-ms-bitfields 使所有结构默认为__attribute__((gcc_struct))
  2. 在结构上显式使用__attribute__((gcc_struct))
  3. 使用#pragma pack 而不是__attribute__((__packed__))

此外,正如@chqrlie 的回答中所指出的那样,您的代码中还有其他一些不理想的地方。尤其是在读取二进制数据时,您通常应该以二进制模式而不是文本模式打开文件,除非您知道自己在做什么(您可能知道,因为文件具有.txt 扩展名)。

【讨论】:

    【解决方案3】:

    由于内存中的数据结构与文件中的数据结构不同,最好将struct的成员一一读取。 例如,有一种方法可以使用“offsetof”指定读取结构成员的位置。 下面使用 fread_members 函数读取 struct 的成员。

    #include <stdio.h>
    #include <stdint.h>
    #include <stddef.h> /* offsetof */
    
    /* offset and size of each member */
    typedef struct {
        size_t offset;
        size_t size;
    } MEMBER;
    
    #define MEMBER_ELM(type, member) {offsetof(type, member), sizeof(((type*)NULL)->member)}
    
    size_t fread_members(void *ptr, MEMBER *members, FILE *stream) {
        char *top = (char *)ptr;
        size_t rs = 0;
        int i;
        for(i = 0; members[i].size > 0; i++){
            rs += fread(top + members[i].offset, 1, members[i].size, stream);
        }
        return rs;
    }
    
    int main(void) {
    
        FILE* file = fopen("216543110.txt", "r");
        if (file==NULL) { return 1; }
    
        typedef struct
        {
            uint16_t twobytes;
            uint32_t fourbytes;
        } FOO;
    
        MEMBER members[] = {
            MEMBER_ELM(FOO, twobytes),
            MEMBER_ELM(FOO, fourbytes),
            {0, 0} /* terminated */
        };
    
        FOO foo;
    
        fread_members(&foo, members, file);
    
        :
    

    【讨论】:

    • 是的,这是一个很好的解决方法,所以我赞成它。但是,它仍然只是一种解决方法,而不是解决潜在问题的方法。有关潜在问题的解决方案,请参阅我的答案。
    • 您对这个问题的回答是直接、确定、简单和出色的。但是,如果数据结构及其结构没有在函数中封闭,对齐问题就得在程序的某个地方解决,复制到另一个结构/数组实例,移植到其他平台,访问没有SIGBUS的成员等等。
    猜你喜欢
    • 2018-08-24
    • 2017-09-22
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 2020-06-12
    • 2013-05-03
    • 1970-01-01
    • 2020-06-14
    相关资源
    最近更新 更多