CS50(2020)恢复程序中的分段错误

Segmentation fault in CS50 (2020) recovery program

我正在尝试编写一个程序来从文件中恢复已删除的图像,并将这些图像中的每一个写入它们自己的单独文件中。我已经在这个问题上停留了几天,并尽力自己解决它,但我现在意识到我需要一些指导。我的代码总是编译得很好,但每次我 运行 我的程序都会遇到分段错误。使用 valgrind 告诉我没有任何内存泄漏。

我想我已经查明了问题所在,但我不确定如何解决它。当我 运行 我的程序通过调试器时,它总是停在我最后一个 'else' 条件内的代码处(评论说“如果已经找到 JPEG”),并给我一条关于分段的错误消息错误。

我已经尝试在这行代码的顶部打开并初始化我的文件指针 jpegn,以防止 jpegn 在这种情况下为 NULL 运行,但这并没有解决错误。

我对编程(和这个网站)还很陌生,所以任何意见或建议都会有所帮助。


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

typedef uint8_t BYTE;

int main(int argc, char *argv[])
{
    if(argc!=2) // Checks if the user typed in exactly 1 command-line argument
    {
        printf("Usage: ./recover image\n");
        return 1;
    }
    
    if(fopen(argv[1],"r") == NULL) // Checks if the image can be opened for reading 
    {
        printf("This image cannot be opened for reading\n");
        return 1;
    }
    
    FILE *forensic_image = fopen(argv[1],"r");  // Opens the image inputted and stores it in a new file
    
    BYTE *buffer = malloc(512 * sizeof(BYTE)); // Dynamically creates an array capable of holding 512 bytes of data
    
    if(malloc(512*sizeof(BYTE)) == NULL) // Checks if there is enough memory in the system
    {
        printf("System error\n");
        return 1;
    }
    
    // Creates a counting variable, a string and two file pointers
    
    int JPEG_num=0;
    char *filename = NULL;
   
    FILE *jpeg0 = NULL;
    FILE *jpegn = NULL;
    
    while(!feof(forensic_image))    // Repeat until end of image
    {
        fread(buffer, sizeof(BYTE), 512, forensic_image); // Read 512 bytes of data from the image into a buffer
        
        // Check for the start of a new JPEG file
        
        if(buffer[0] == 0xff & buffer[1] == 0xd8 & buffer[2] == 0xff & (buffer[3] & 0xf0) == 0xe0)
        {
            // If first JPEG
            
            if(JPEG_num == 0)
            {
                sprintf(filename, "%03i.jpg", JPEG_num);
                jpeg0 = fopen(filename, "w");
                fwrite(buffer, sizeof(BYTE), 512, jpeg0);
            }
            else    // If not first JPEG
            {
                fclose(jpeg0);
                JPEG_num++;
                
                sprintf(filename, "%03i.jpg", JPEG_num);
                jpegn = fopen(filename, "w");
                fwrite(buffer, sizeof(BYTE), 512, jpegn);
             }
            
        }
        else    // If already found JPEG
        {
            fwrite(buffer, sizeof(BYTE), 512, jpegn);
        }
        
        
    }
    
    // Close remaining files and free dynamically allocated memory

    fclose(jpegn);
    
    free(buffer);
    
}


你的代码有很多问题。如果 valgrind 没有识别出这些,我感到很惊讶。

首先是这个:

    if(fopen(argv[1],"r") == NULL) // Checks if the image can be opened for reading 
    {
        printf("This image cannot be opened for reading\n");
        return 1;
    }
    
    FILE *forensic_image = fopen(argv[1],"r");  // Opens the image inputted and stores it in a new file

这不是致命的,但您打开同一个文件两次并丢弃了第一个文件指针。但是下面类似模式的肯定是内存泄漏:

    BYTE *buffer = malloc(512 * sizeof(BYTE)); // Dynamically creates an array capable of holding 512 bytes of data
    
    if(malloc(512*sizeof(BYTE)) == NULL) // Checks if there is enough memory in the system
    {
        printf("System error\n");
        return 1;
    }

在这里你分配了两次 512 字节并且只在指针中保留第一次分配 buffer,而第二次分配丢失了。

然后是这个:

    char *filename = NULL;

    // ...   
    
    sprintf(filename, "%03i.jpg", JPEG_num);

您正在将字符串写入未分配的内存!

还有几行:

        else    // If already found JPEG
        {
            fwrite(buffer, sizeof(BYTE), 512, jpegn);
        }

如何保证 jpegn 是一个有效的文件指针?可能永远不会,因为我在您的代码中看到 JPEG_num 将始终为 0。else 中由 // If not first JPEG 标记的部分是死代码。

编译时,始终启用警告,然后修复这些警告。

gcc -ggdb3 -Wall -Wextra -Wconversion -pedantic -std=gnu11 -c "untitled1.c" -o "untitled1.o" 

导致几个警告,例如:

untitled1.c:46:91: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]

if(buffer[0] == 0xff & buffer[1] == 0xd8 & buffer[2] == 0xff & (buffer[3] & 0xf0) == 0xe0) 

注意:单个 & 有点明智 AND。你真的想要一个符合逻辑的 AND && 除了最后一个语句

关于;

FILE *forensic_image = fopen(argv[1],"r"); 

始终检查 (!=NULL) 返回值以确保操作成功。如果不成功 (==NULL) 则调用

perror( "fopen failed" ); 

将您的错误消息和系统认为发生错误的文本原因输出到 stderr

关于:

while(!feof(forensic_image)) 

请阅读:why while( !feof() is always wrong

关于:

FILE *forensic_image = fopen(argv[1],"r"); 

这已经在前面的代码块中完成了。绝对没有理由再次这样做,并且会在代码中产生问题。建议:替换:

if(fopen(argv[1],"r") == NULL)      
{         
    printf("This image cannot be opened for reading\n");
    return 1;     
} 

与:

if( (forensic_image = fopen(argv[1],"r") ) == NULL)      
{         
    perror( "fopen for input file failed" );         
    exit( EXIT_FAILURE );     
}

关于:

BYTE *buffer = malloc( 512 * sizeof(BYTE) );

及以后:

free( buffer );

这是对代码和资源的浪费。该项目只需要一个这样的实例。建议:

#define RECORD_LEN 512 

unsigned char buffer[ RECORD_LEN ]; 

关于;

fread(buffer, sizeof(BYTE), 512, forensic_image); 

函数:fread()returns一个size_t。您应该将返回值分配给 size_t 变量并检查该值以确保操作成功。事实上,该语句应该在 while() 条件

关于;

sprintf(filename, "%03i.jpg", JPEG_num); 

这会导致未定义的行为,并可能导致段错误事件,因为指针 filename 被初始化为 NULL。建议:

char filename[20]; 

避免这个问题

关于:

else    // If not first JPEG             
{                 
    fclose(jpeg0); 

如果您(例如)使用第三个文件,则 jpeg0 已经关闭,导致 运行 时间错误。建议删除语句:

FILE *jpeg0;

并且总是使用 jpegn

关于;

else    // If already found JPEG         
{             
    fwrite(buffer, sizeof(BYTE), 512, jpegn);         
} 

在第一个输出文件中,jpegn 未设置,因此导致崩溃。同样,所有输出文件操作仅使用 jpegn

关于:

fwrite(buffer, sizeof(BYTE), 512, jpegn); 

这个returns实际写入的(第二个参数)数量,所以应该是:

if( fwrite(buffer, sizeof(BYTE), 512, jpegn) != 512 ) { // handle error } 

发布的代码包含一些 'magic' 数字,例如 512。'magic' 数字是没有基础的数字。 'magic' 数字使代码更难理解、调试等。建议使用 enum 语句或 #define 语句为这些 'magic' 数字赋予有意义的名称,然后使用它们整个代码中有意义的名称。