cs50 第四週的課程核心為 memory 以及 pointer 的介紹,由於 pointer 的概念比較複雜,所以我看了兩次才稍微進入狀況 💦
老師在課堂最後面也快速介紹了如何用 C 語言開啟、讀取、寫入文件,而實際上的運用就是 problem set 4 的 recover 問題了。這個問題折騰了我快一天,好不容易通過了所有檢核,因此希望記錄下來,順便整理一下解題的思路。
我們要用 C 語言寫程式,透過遞迴的方式從記憶卡找尋 JPEG 圖檔。記憶卡由許多 512 bytes 的區塊組成。JPEG 圖檔的首四個 bytes 則有固定的格式。每當我們發現新的 JPEG 圖檔,可以開啟一份新檔案,將 bytes 從記憶卡寫入到新檔案裡面。
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 需要帶入兩個參數,./recover
以及 IMAGE,其中 IMAGE 就是我們要讀取的檔案 (記憶卡)。所以真實運作起來會長這樣:
$ ./recover card.raw // card.raw 就是要讀取的記憶卡
#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;
}
FILE *fopen(const char *pathname, const char *mode);
在這個問題中,我們會用到兩種 mode:
"r"
:代表檔案讀取模式。"w"
:代表檔案寫入模式。size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
在本次的問題中,我們使用了 while 迴圈來重複檢查記憶卡眾多的 512 bytes 區塊群當中,是否出現 JPEG header 的蹤跡。while 迴圈的條件就是透過 fread()
來實現的。
while (fread(buffer, 512, 1, input_file) == 1)
💡 fread()
會回傳讀取的項目數量,如果到達被讀取的檔案底端,則會回傳 0 或是小於 nmemb 的數字。