cs50 第四週的課程核心為 memory 以及 pointer 的介紹,由於 pointer 的概念比較複雜,所以我看了兩次才稍微進入狀況 💦
老師在課堂最後面也快速介紹了如何用 C 語言開啟、讀取、寫入文件,而實際上的運用就是 problem set 4 的 recover 問題了。這個問題折騰了我快一天,好不容易通過了所有檢核,因此希望記錄下來,順便整理一下解題的思路。
背景說明
我們要用 C 語言寫程式,透過遞迴的方式從記憶卡找尋 JPEG 圖檔。記憶卡由許多 512 bytes 的區塊組成。JPEG 圖檔的首四個 bytes 則有固定的格式。每當我們發現新的 JPEG 圖檔,可以開啟一份新檔案,將 bytes 從記憶卡寫入到新檔案裡面。JPEG 規則
JPEG 圖檔的頭三個 bytes 一定是 0xff
、0xd8
、0xff
,而第四個 byte 則可能是 0xe0
到 0xef
其中一個。看到 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 就是要讀取的記憶卡
解題思路
- 確認使用者有沒有乖乖在 command-line 提供兩個參數
- 打開要讀取的檔案 (記憶卡)
- 確認檔案 (記憶卡) 能否開啟
- 宣告待會將用到的變數
- 讀取檔案 (記憶卡) 眾多 512 bytes
- 尋找 JPEG header 的蹤跡 (前四個 bytes)
- 有找到 JPEG header 的話,建立新檔案的名稱
- 打開新檔案 (output_file)
- 找到的 JPEG 圖檔數量加一 (紀錄數量是為了檔案名稱)
- 確認新檔案可以寫入,然後寫入
- 關閉檔案,並釋放記憶體空間
#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 的數字。