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

CS50 Recover程序仍存在可访问内存问题,Valgrind无错误求助

调试CS50 recover程序的问题排查

我调试CS50的recover程序已经耗了好几个小时,还没找到问题所在。我认为所有文件都已关闭,且未使用malloc进行内存分配。恳请提供排查提示或指导,非常感谢!

Valgrind检测结果

==34245== Memcheck, a memory error detector
==34245== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==34245== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==34245== Command: ./recover card.raw
==34245== 
==34245== 
==34245== HEAP SUMMARY:
==34245==     in use at exit: 944 bytes in 2 blocks
==34245==   total heap usage: 104 allocs, 102 frees, 233,912 bytes allocated
==34245== 
==34245== 944 bytes in 2 blocks are still reachable in loss record 1 of 1
==34245==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==34245==    by 0x4A076CD: __fopen_internal (iofopen.c:65)
==34245==    by 0x4A076CD: fopen@@GLIBC_2.2.5 (iofopen.c:86)
==34245==    by 0x1092A9: main (recover.c:48)
==34245== 
==34245== LEAK SUMMARY:
==34245==    definitely lost: 0 bytes in 0 blocks
==34245==    indirectly lost: 0 bytes in 0 blocks
==34245==      possibly lost: 0 bytes in 0 blocks
==34245==    still reachable: 944 bytes in 2 blocks
==34245==         suppressed: 0 bytes in 0 blocks
==34245== 
==34245== For lists of detected and suppressed errors, rerun with: -s
==34245== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

我的代码

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


int main(int argc, char *argv[])
{
    // Ensure proper usage
    if (argc != 2)
    {
        printf("Usage: ./recover card\n");
        return 1;
    }

    // If the forensic image cannot be opened for reading, your program should inform the user as much, and main should return 1.
    // Remember filenames
    char *infile = argv[1];
    // Open input file
    FILE *inptr = fopen(infile, "r");

    if (inptr == NULL)
    {
        printf("Could not open %s.\n", infile);
        fclose(inptr);
        return 1;
    }

    // block size is 512 bytes
    typedef uint8_t BYTE;

    int BLOCK_SIZE = sizeof(BYTE) * 512;

    BYTE buffer[BLOCK_SIZE];

    int index = 0;

    FILE *img = NULL;

    // The files you generate should each be named ###.jpg, where ### is a three-digit decimal number, starting with 000 for the first image and counting up.
    while (fread(buffer, 1, BLOCK_SIZE, inptr) == BLOCK_SIZE)
    {
        char filename[8];

        // is first pic.
        if (index == 0) {

            sprintf(filename, "%03i.jpg", index);
            img = fopen(filename, "w");

            if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0) {
                if (img != NULL) {
                    fwrite(buffer, 1, BLOCK_SIZE, img);
                }
                index++;
            }
        } else {
            //is not first pic
            if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0) {
                //is a new img
                //close prev img
                if (img != NULL) {
                    fclose(img);
                }
                // open a new img
                sprintf(filename, "%03i.jpg", index);
                img = fopen(filename, "w");
                fwrite(buffer, 1, BLOCK_SIZE, img);
                index++;
            } else {
                //keep writing
                fwrite(buffer, 1, BLOCK_SIZE, img);
            }
        }
    //Your program, if it uses malloc, must not leak any memory.
    }
    if (img != NULL) {
        fclose(img);
    }
    fclose(inptr);
    return (0);
}

排查提示与问题修正

1. 错误的文件打开时机(导致文件句柄泄漏)

在index == 0的分支中,你先打开了000.jpg,再检查当前块是否是JPG签名。如果第一个块不是JPG起始签名,这个文件会被打开但不会写入内容,且后续循环会再次打开同一个文件,覆盖img指针,导致之前的文件句柄丢失——这正是Valgrind检测到的still reachable内存块的来源。

修正: 先检查当前块是否符合JPG签名,确认是之后再打开文件:

if (index == 0) {
    if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0) {
        sprintf(filename, "%03i.jpg", index);
        img = fopen(filename, "w");
        if (img != NULL) {
            fwrite(buffer, 1, BLOCK_SIZE, img);
        }
        index++;
    }
}

2. fclose(NULL)的无效操作

当inptr为NULL时,你调用了fclose(inptr),但NULL指针不需要关闭,这会引发未定义行为。

修正: 删除fclose(inptr);这一行:

if (inptr == NULL)
{
    printf("Could not open %s.\n", infile);
    return 1;
}

3. 未处理最后一个不足512字节的块

循环仅处理fread返回等于BLOCK_SIZE的情况,但文件末尾可能存在不足512字节的块,这部分属于最后一个JPG的内容,需要写入。

修正: 记录fread的返回值,循环退出后如果img不为空且读取到字节,就写入:

size_t bytes_read;
while ((bytes_read = fread(buffer, 1, BLOCK_SIZE, inptr)) == BLOCK_SIZE) {
    // 原循环逻辑
}
// 处理最后一个块
if (img != NULL && bytes_read > 0) {
    fwrite(buffer, 1, bytes_read, img);
}

4. 潜在的空指针fwrite风险

在非第一个文件的分支中,如果还未找到第一个JPG(img仍为NULL),直接调用fwrite会导致错误。需要先判断img是否有效再写入:

修正:

} else {
    //keep writing
    if (img != NULL) {
        fwrite(buffer, 1, BLOCK_SIZE, img);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:05:56