fread() 的返回值出乎意料。

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

int main()
{
    FILE* bmp = NULL;
    uint32_t offset;
    uint8_t* temp = NULL;
    size_t read;
    unsigned int x_dim = 600, y_dim = 388;

    bmp = fopen("test_colour.bmp", "r");

    if (!bmp)
        return -1;

    /* Get the image data offset */
    fseek(bmp, 10, SEEK_SET);
    fgets((char*)&offset, 4, bmp);

    printf("Offset = %u\n", offset);

    temp = malloc(3*x_dim*y_dim*sizeof(uint8_t));

    if (!temp)
        return -1;

    /* Go the the position where the image data is stored */
    fseek(bmp, offset, SEEK_SET);

    /* Copy image data to array */
    printf("%u bytes requested!\n", 3*x_dim*y_dim);
    read = fread((void*)temp, sizeof(uint8_t), 3*x_dim*y_dim, bmp);
    printf("%Iu bytes read!\n", read);

    fclose(bmp);
    free(temp);

    return 0;
}

我使用上述代码将24比特每像素的BMP图像的RGB数据读入一个数组。根据BMP规范,图像数据从文件开头到图像数据开始处(在BMP标题后面)的偏移量在偏移量10处给出。执行上述代码时,我得到以下输出。

Offset = 54
698400 bytes requested!
33018 bytes read!

偏移量输出看起来是正确的,因为文件大小为698454字节(=698400 + 54)。然而,fread()返回的值似乎表明并没有读取整个图像数据。不过,我随后使用temp数组中的数据将RGB数据转换为灰度数据,并将这些数据再次写入BMP文件。目测检查输出图像并未出现任何错误,即使fread()表明情况与此不同,好像我在一开始就实际读取了整个输入图像。

有人能解释这种行为吗?


两个问题:1)在调用fread后,您可以检查temp的内容,以查看它是否在读取了33018个字节后停止了吗?2)我不熟悉%Iu格式说明符-您可以在调试器中检查read的实际值吗? - Treb
我不认为这是导致您看到的症状的原因,但您应该使用正确的 printf 格式字符串。 size_t 的格式为 "%zu""%Iu" 是非标准的。 如果您的实现不支持 "%zu",则可以使用 printf("%lu bytes read!\n", (unsigned long)read); - Keith Thompson
@simon:请查看https://dev59.com/K2gv5IYBdhLWcg3wVPXU#10680635,获取有关为什么MinGW支持的printf()格式说明符可能会有点混淆的一些信息。更新的MinGW版本和/或更新的MSVCRT.DLL版本似乎支持越来越多的C99标准说明符。 - Michael Burr
然而,我随后使用临时数组中的数据将RGB数据转换为灰度,并将此数据再次写入BMP文件。您能测试新BMP文件的大小吗?它是否大于33 KB?新文件的尺寸是否为600 * 388像素? - Hicham
是的,没错。当我使用“rb”时它可以正常工作。唯一的问题似乎是在Windows下使用“r”。 - simon
显示剩余2条评论
1个回答

32

我敢打赌你在使用Windows操作系统。

bmp = fopen("test_colour.bmp", "r");

应该是

bmp = fopen("test_colour.bmp", "rb");
如果在Windows上以文本模式打开文件,运行时会在读取到0x1a(Ctrl-Z)字节时停止读取,这是Windows认为的文本文件的EOF标记。即使它没有读取到 Ctrl-Z,当Windows将CR/LF序列转换为单个LF字符时,您也会得到损坏的数据。
您能够从缓冲区呈现图像,因为fread()实现确实会读取您请求的字节数(或接近该数字 - 该数字会向下舍入为某些块大小的倍数),然后它会扫描缓冲区寻找要转换和Ctrl-Z EOF标志的CR/LF序列。
因此,即使fread()返回33018,缓冲区实际上已经几乎完全被写入了来自文件的数据。 数据不是100%正确的(例如,某些CR字符可能被丢弃)或完整的,但在这种情况下足够接近以呈现看起来像您预期的图像。
当然,这仅是观察到这种特定运行时当前的行为方式 - 它在未来可能不会一直以这种方式表现(即使在今天的所有系统上也可能如此)。

确实是这样,我在原始帖子中忘记提到了。感谢您指出这一点,问题已经解决了!您确定它实际上停止读取了吗?正如我所提到的,尽管根据“fread”的说法只有约5%的文件数据被读取,但我仍然能够输出整个BMP图像的正确灰度图像。 - simon
@simon:可能情况是运行时将您请求的字节数读入缓冲区,然后通过它进行“修复”行结束和EOF转换。但我只是猜测。 - Michael Burr
我比较了使用“r”和“rb”打开文件生成的两个输出图像,并使用diff进行了比较,结果不同。从视觉上看,这两个图像唯一的区别似乎是“r”图像中最后几个像素是黑色的,而实际上应该是白色的。因此,我猜测fread实际上读取的字节数比返回值所指示的要多,但并不一定是请求的实际字节数。 - simon
在“r”模式下,由于它们的值,某些字节不会被读取。因此,在“r”模式下读取的字节数比“rb”模式下少是正常的。 “r”模式仅用于从文本文件中读取。 - Hicham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接