2023-09-29|閱讀時間 ‧ 約 8 分鐘

cs50 week 4 問題:Recover

cs50 第四週的課程核心為 memory 以及 pointer 的介紹,由於 pointer 的概念比較複雜,所以我看了兩次才稍微進入狀況 💦

老師在課堂最後面也快速介紹了如何用 C 語言開啟、讀取、寫入文件,而實際上的運用就是 problem set 4 的 recover 問題了。這個問題折騰了我快一天,好不容易通過了所有檢核,因此希望記錄下來,順便整理一下解題的思路。

背景說明

我們要用 C 語言寫程式,透過遞迴的方式從記憶卡找尋 JPEG 圖檔。記憶卡由許多 512 bytes 的區塊組成。JPEG 圖檔的首四個 bytes 則有固定的格式。每當我們發現新的 JPEG 圖檔,可以開啟一份新檔案,將 bytes 從記憶卡寫入到新檔案裡面。

JPEG 規則

JPEG 圖檔的頭三個 bytes 一定是 0xff0xd80xff,而第四個 byte 則可能是 0xe00xef 其中一個。看到 0x 我們其實就知道這些 byte 是用十六進位制

每個 JPEG 彼此緊貼延續,所以當我們辨識出 JPEG header 出現,也代表上一個 JPEG 的結束。還有一點值得注意,JPEG 不會只剛好佔據一個 512 byte 的區塊,而是會根據圖檔的大小而定。參考 problem set 的 walkthrough 影片更加清楚:

檔案名稱規定

當我們開啟一份新檔案想要寫入記憶卡的 bytes 時,要注意題目有規定檔案名稱的寫法。檔案名稱必須為 ###.jpg。其中 ### 代表三個十進位的數碼,以 000 作為第一個檔案的名稱。換句話說,若是第三個檔案,其名稱將為 003.jpg

Command-line argument

command-line 需要帶入兩個參數,./recover 以及 IMAGE,其中 IMAGE 就是我們要讀取的檔案 (記憶卡)。所以真實運作起來會長這樣:

$ ./recover card.raw // card.raw 就是要讀取的記憶卡



解題思路

  1. 確認使用者有沒有乖乖在 command-line 提供兩個參數
  2. 打開要讀取的檔案 (記憶卡)
  3. 確認檔案 (記憶卡) 能否開啟
  4. 宣告待會將用到的變數
  5. 讀取檔案 (記憶卡) 眾多 512 bytes
  6. 尋找 JPEG header 的蹤跡 (前四個 bytes)
  7. 有找到 JPEG header 的話,建立新檔案的名稱
  8. 打開新檔案 (output_file)
  9. 找到的 JPEG 圖檔數量加一 (紀錄數量是為了檔案名稱)
  10. 確認新檔案可以寫入,然後寫入
  11. 關閉檔案,並釋放記憶體空間
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef uint8_t BYTE;

int main(int argc, char *argv[])
{
    // Check if user provides two arguments in command-line
    if (argc != 2)
    {
        printf("Usage: ./recover IMAGE\n");
        return 1;
    }

    // Open the file for reading
    FILE *input_file = fopen(argv[1], "r");

    // Check the file can be opened
    if (input_file == NULL)
    {
        printf("This file can not be opened!\n");
        return 1;
    }

    // Declare the vars
    BYTE buffer[512];
    int image_counter = 0;

    //Initialize the output file
    FILE *output_file = NULL;

    // ***.JPG (with a space for /n at the end)
    char *filename = malloc(8 * sizeof(char));

    // Read the blocks
    while (fread(buffer, 512, 1, input_file) == 1)
    {
        // Identify JPEG signature
        if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0)
        {
            // Create filename
            sprintf(filename, "%03i.jpg", image_counter);

            // Open outputfile for writing
            output_file = fopen(filename, "w");

            // Image Counter increased by 1
            image_counter++;
        }

        // If JPEG has been found then write into output file
        if (!(image_counter == 0))
        {
            fwrite(buffer, 512, 1, output_file);
        }
    }

    // Close the files
    fclose(input_file);
    fclose(output_file);

    // Release memory spaces allocated by malloc
    free(filename);

    return 0;
}


要點筆記

fopen() 開啟檔案

FILE *fopen(const char *pathname, const char *mode);

在這個問題中,我們會用到兩種 mode:

  • "r":代表檔案讀取模式。
  • "w":代表檔案寫入模式。

fread() 讀取檔案

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • ptr:要讀取資料的記憶體位址
  • size:要讀取的資料類型大小 (以 byte 當作單位)
  • nmemb:一次要讀取的資料大小
  • stream:被讀取的檔案的 pointer

在本次的問題中,我們使用了 while 迴圈來重複檢查記憶卡眾多的 512 bytes 區塊群當中,是否出現 JPEG header 的蹤跡。while 迴圈的條件就是透過 fread() 來實現的。

while (fread(buffer, 512, 1, input_file) == 1)
💡 fread() 會回傳讀取的項目數量,如果到達被讀取的檔案底端,則會回傳 0 或是小於 nmemb 的數字。
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.