fread():从文件中读取(不对齐)导致跳过字节

fread(): Reading from a file (without alignment) results in skipping of bytes

我有一个文件并使用 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 11 66 55 44 33 ...


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

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

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

gcc online user's guide 记录了 __attribute__ ((__packed__)) 类型属性,并举例说明了该属性位于定义的 { 之前。

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

另请注意以下备注:

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

由于内存中的数据结构与文件中的数据结构不同,所以struct的成员一个一个读取可能会更好。 例如,有一种方法可以指定位置来读取带有“offsetof”的struct 成员。 下面用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);

    :

在 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 扩展名)。