(我这样做是为了一个个人项目--只是为了学习指针/引用/内存管理之类的东西如何在C语言中工作.我想"不作弊",也就是说,没有任何超抽象库或语言的帮助.因此,C语言是首选语言.)

很简单,我正在try 将带有原始数据的位图文件的头文件读入传递给函数的 struct 实例.首选工具-ChatGPT和GCC推荐的fread(),因为这是您在Linux Ubuntu终端上通常使用的.

以下是传统的54字节头BMP格式的代码(请注意,字节填充问题已得到处理):

#ifndef BMPHEADER_H
#define BMPHEADER_H
#include <stdint.h> // For fixed-width types                                                                                                                  

#pragma pack(push, 1) // Ensure that structure is packed without padding                                                                                      
typedef struct {
  uint16_t signature; // BM                                                                                                                                   
  uint32_t fileSize;
  uint16_t reserved1;
  uint16_t reserved2;
  uint32_t dataOffset;
  uint32_t headerSize;
  int32_t width;
  int32_t height;
  uint16_t planes;
  uint16_t bitsPerPixel;
  uint32_t compression;
  uint32_t rawBmpSize;
  uint32_t horizRes;
  uint32_t vertRes;
  uint32_t numColors;
  uint32_t numImptColors;
} BmpHeader;
#pragma pack(pop)

#endif

下面是实例化包含头的 struct 并提供令人满意的结果的代码:

void run_core_funcs(void)
{
    printf("Running core OCR and I/O functions ...\n");

    ProcList pm;
    BmpHeader bh;
    get_fname(pm.fname);

    FILE * src = fopen(pm.fname, "rb");
    fread(&bh, sizeof(bh), 1, src);
    printf("Sig: %x \n", bh.signature);
    printf("File size: %x \n", bh.fileSize);
    printf("Res 1: %x \n", bh.reserved1);
    printf("Res 2: %x \n", bh.reserved2);
    printf("Offset: %x \n", bh.dataOffset);
    fclose(src);

    get_img_props(&pm, &bh);

请注意,我正在调用一个不同的函数,并通过它们的指针传递处理规范管理器和位图标头(因为C没有用于按引用传递的构造).这就是事情变得有趣的地方.

void get_img_props(ProcList * pm, BmpHeader * imgHdr)
{
  printf("Caching image data from %s ...\n", pm->fname);

  FILE * src = fopen(pm->fname, "rb");
  fread(imgHdr, sizeof(imgHdr), 1, src);
  printf("Sig: %x \n", imgHdr->signature);
  printf("File size: %x \n", imgHdr->fileSize);
  printf("Res 1: %x \n", imgHdr->reserved1);
  printf("Res 2: %x \n", imgHdr->reserved2);
  printf("Offset: %x \n", imgHdr->dataOffset);
  fclose(src);

是的,重复代码,只是为了确保一切正常.

下面是"外部"和"内部"两个函数的满意结果.(是的,我知道这些字段表明文件大小、数据偏移量和信息头大小应该非常大,但这不是重点.)

Sig: 4d42 
File size: 7e98a 
Res 1: 0 
Res 2: 0 
Offset: 8a 

然而,...一旦我注释掉run_core_uncs()中的原始数据检索代码,通过它们的指针传递 struct 实例,并且只在get_img_props()中执行原始数据检索,我就得到了一些不同的东西:

结果:

Sig: 4d42 
File size: 7e98a 
Res 1: 0 
Res 2: 3 
Offset: 0 

为什么会这样呢?与其说我做错了什么,不如说我更倾向于相信C中指针的工作方式或GCC处理事物的方式存在固有的错误实现.但我知道什么呢.

我在不同的平台(GoogleSOthis Reddit one is not particularly helpful)上做了一些研究,我得到的唯一结果是与指针和引用的混淆有关的通用命中,以及对内存开销的担忧等.ChatGPT也没有真正做任何帮助,提出了各种不同的要点,都是我的错,我应该判断我清晰的工作中是否有错误,并说明我已经考虑到的所有问题-将地址传递给 struct 对象的质量、数据对齐要求等.还有一个提醒:我正在使用GCC.

推荐答案

void get_img_props(ProcList * pm, BmpHeader * imgHdr) {
   // ...
   fread(imgHdr, sizeof(imgHdr), 1, src);

sizeof(imgHdr)是指针的大小.因此,您只读取8字节(假设是64位系统),而不是54字节.

你想要sizeof *imgHdr或者sizeof(BmpHeader).

当您在外部函数中填充 struct 时,您没有注意到内部函数中的问题,因为最后46个字节已经包含了以前填充时的正确数据.但如果不这样做,这46个字节将完全未初始化并包含垃圾.

判断返回值fread和所有其他系统/库函数通常是必不可少的,这将帮助您更快地找到它.我怀疑valgrind也会发现未初始化内存的使用.

我不太倾向于相信我做错了什么,而不是[...]

Obligatory;-)

C++相关问答推荐

插入元素后,Sizeof操作符无法正常工作

当我try 计算一个多项时,看到segfault.我做错了什么?

生成C代码时自动复制/生成' tmwtypes.h '依赖项

strftime函数中%s的历史意义是什么?为什么没有记录?

为什么这个C程序代码会产生以下结果?

当main函数调用被重构时,C函数给出错误的结果

从内联程序集调用Rust函数和调用约定

在C语言中使用scanf()时我无法理解的警告

手动矢量化性能差异较大

为什么GCC C23中的关键字FALSE不是整数常量表达式?

fwrite无法写入满(非常大)缓冲区

进程在写入管道时挂起

在创建动态泛型数组时,通过realloc对故障进行分段

C堆栈(使用动态数组)realloc内存泄漏问题

有没有一种方法可以用C创建保留限定符的函数?

将 struct 数组写入二进制文件时发生Valgrind错误

如何在不读取整个字符串的情况下删除UTF8字符串的尾随空格以提高性能?

我不知道为什么它不能正常工作,我用了get()和fget(),结果是一样的

在Ubuntu上使用库部署C程序的最佳实践

C: NULL>;NULL总是false?