3

我有一个文件,并且使用 CI 想要使用 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 是否将结构成员存储为 or ,我很困惑为什么它无法正确读取我的文件。22 11 .. .. 66 55 44 33 ...22 11 66 55 44 33 ...

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

4

3 回答 3

3

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

gcc 在线用户指南使用__attribute__ ((__packed__))示例记录type 属性,其中该属性放置在{定义之前。

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

另请注意以下备注:

  • 该文件应以二进制模式打开,带有"rb",
  • sizeof(FOO)参数应该被转换(int)%d转换说明符。
  • 的指针参数%p应该转换为(void *).
  • foo.twobytes与 具有相同的地址foo,这是 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;
}
于 2020-07-23T19:57:24.397 回答
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扩展名)。

于 2020-07-24T14:28:38.503 回答
1

由于内存中的数据结构与文件中的数据结构不同,因此最好将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);

    :
于 2020-07-24T06:11:58.137 回答