You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CS50 Pset4 Recover任务:JPEG识别计数异常求助

CS50 Pset4 Recover任务:JPEG计数错误排查

问题背景

正在完成CS50课程Pset4的Recover任务,需要从课程提供的card.raw文件中识别隐藏的JPEG文件。已知该文件恰好包含50个JPEG,但编写的统计程序输出计数结果为860,明显不符合预期。

JPEG文件的签名规则:

  • 前三个字节为0xff 0xd8 0xff
  • 第四个字节十六进制值介于0xe0到0xef之间(即前4位为1110,对应十进制14)

原代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Variable used for bits reading when looking for JPEGs files
int msb = 8, lsb = 4;

int main(int argc, char *argv[])
{
    // Find JPEG
    char *temp = malloc(3);
    int *temp1 = malloc(1);
    int counter = 0;

    while (fread(temp, 3, 1, card))
    {
        if(strcmp(temp, "0xff 0xd8 0xff"))
        {
            fread(temp1, 1, 1, card);
            int result = (*temp1 >> lsb) & ~(~0 << (msb-lsb+1));
            if(result == 14)
            {
                fseek(card, 508, SEEK_CUR);
                counter++;
            }
        }
    }
    printf("counter = %i\n", counter);
}

问题排查与修复

你的核心逻辑方向是对的,但代码里有几个关键错误导致计数严重偏高:

1. 错误使用strcmp匹配二进制签名

strcmp是用来比较ASCII字符串的,但你用它来对比temp里存储的二进制字节数据,这完全不匹配。比如字符串"0xff 0xd8 0xff"是由'0'、'x'、'f'等ASCII字符组成的,和JPEG签名的二进制字节0xff、0xd8、0xff没有任何关系。

同时,strcmp的返回值是0表示两个字符串相等,你写的if(strcmp(...))会在不相等的时候执行代码块,逻辑完全反了。

修复方式:直接逐个字节对比二进制值:

if (temp[0] == 0xff && temp[1] == 0xd8 && temp[2] == 0xff)

2. 未定义文件指针card

代码里直接使用了card这个文件指针,但既没有通过fopen打开文件,也没有定义这个变量。必须先检查命令行参数,然后打开文件:

if (argc != 2)
{
    fprintf(stderr, "Usage: %s image\n", argv[0]);
    return 1;
}

FILE *card = fopen(argv[1], "rb");
if (card == NULL)
{
    fprintf(stderr, "Could not open %s.\n", argv[1]);
    return 1;
}

注意要以二进制模式"rb"打开,避免系统自动转换换行符干扰二进制数据。

3. 循环读取逻辑错误

当前循环每次读取3字节,如果这3字节不是JPEG签名,文件指针会直接前进3字节,这会跳过中间可能存在的签名位置(比如签名的起始位置在第2字节,你读了1-3字节后,下次读4-6字节,就错过了2-4字节的签名)。

正确的做法是:不匹配时回退2字节,让下次循环从下一个字节开始读取:

// 不匹配时,回退2字节
fseek(card, -2, SEEK_CUR);

4. 第四个字节的判断可以简化

你用移位和掩码的方式判断前4位,虽然逻辑正确,但可以更直观:直接用掩码0xf0提取前4位,判断是否等于0xe0:

if ((*temp1 & 0xf0) == 0xe0)

5. 不必要的动态内存分配

对于3字节和1字节的小缓冲区,完全不需要用malloc,直接用栈上的数组/变量即可,避免内存泄漏:

unsigned char temp[3]; // 用unsigned char避免符号位干扰
unsigned char temp1;

6. fseek的错误使用

你找到签名后跳过508字节,假设每个JPEG占512字节,但实际上JPEG可能由多个512字节块组成,而且这样跳转会因为错误匹配的假签名被计数,导致总数偏高。如果只是计数,应该继续从当前位置往后找下一个签名,而非直接跳转(示例代码中保留跳转仅为适配你当前的计数需求,实际恢复JPEG时不能这么做)。

修正后的示例代码

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

int main(int argc, char *argv[])
{
    // 检查命令行参数
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s image\n", argv[0]);
        return 1;
    }

    // 打开文件
    FILE *card = fopen(argv[1], "rb");
    if (card == NULL)
    {
        fprintf(stderr, "Could not open %s.\n", argv[1]);
        return 1;
    }

    unsigned char temp[3];
    unsigned char temp1;
    int counter = 0;

    while (fread(temp, 3, 1, card) == 1)
    {
        // 匹配前三个字节
        if (temp[0] == 0xff && temp[1] == 0xd8 && temp[2] == 0xff)
        {
            // 读取第四个字节
            if (fread(&temp1, 1, 1, card) == 1)
            {
                // 判断第四个字节的前4位
                if ((temp1 & 0xf0) == 0xe0)
                {
                    counter++;
                    // 仅用于计数场景:跳过当前块剩余字节避免重复计数
                    fseek(card, 508, SEEK_CUR);
                }
                else
                {
                    // 第四个字节不匹配,回退1字节
                    fseek(card, -1, SEEK_CUR);
                }
            }
        }
        else
        {
            // 前三个字节不匹配,回退2字节
            fseek(card, -2, SEEK_CUR);
        }
    }

    printf("counter = %i\n", counter);

    // 关闭文件
    fclose(card);
    return 0;
}

新手友好提示

  • 二进制文件操作一定要用"rb"(读)或"wb"(写)模式,否则Windows系统会把0x0a转换为0x0d0a,破坏二进制数据。
  • 用unsigned char存储二进制字节,避免符号位导致的数值错误(比如0xff作为signed char会被解析为-1)。
  • 每次调用文件操作函数(fread、fseek等)最好检查返回值,确保操作成功。
  • 动态分配的内存记得用free释放,虽然小程序可能不会有问题,但养成好习惯很重要。

内容的提问来源于stack exchange,提问作者Freddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 17:48:18