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

时间:2020-08-20 04:20:53

标签: c segmentation-fault cs50

我正在尝试编写一个程序,该程序将从文件中恢复已删除的图像,并将这些图像中的每一个写入各自的单独文件中。我已经在这个问题上停留了几天,并且已经尽力解决了这个问题,但是现在我意识到我需要一些指导。我的代码总是可以很好地编译,但是每次运行程序时,我都会遇到分段错误。使用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);
    
}


2 个答案:

答案 0 :(得分:2)

您的代码中有很多问题。如果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的部分是无效代码。

答案 1 :(得分:0)

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

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) 

注意:单个&有点与。您真的想要除此语句中的最后一个语句外的所有逻辑和键&&

关于;

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()返回一个size_t。您应该将返回的值分配给size_t变量,并检查该值以确保操作成功。实际上,该语句应处于while()条件下

关于;

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

这将导致不确定的行为,并可能导致段错误事件,因为指针filename已初始化为NULL。建议:

char filename[20]; 

避免该问题

关于:

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

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

FILE *jpeg0;

并始终使用jpegn

关于;

else    // If already found JPEG         
{             
    fwrite(buffer, sizeof(BYTE), 512, jpegn);         
} 
在第一个输出文件上未设置

jpegn,因此会导致崩溃。同样,仅对所有输出文件操作使用jpegn

关于:

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

这将返回实际写入的(第二个参数)数量的数量,因此应为:

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

发布的代码包含一些“魔术”数字,例如512。“魔术”数字是没有基础的数字。 “魔术”数字使代码更难以理解,调试等。建议使用enum语句或#define语句为这些“魔术”数字赋予有意义的名称,然后在整个过程中使用这些有意义的名称。代码。