cs50 week 4 問題:Recover

更新於 發佈於 閱讀時間約 7 分鐘

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 影片更加清楚:

raw-image

檔案名稱規定

當我們開啟一份新檔案想要寫入記憶卡的 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 的數字。
隨處可見的台灣普男,育有一貓,最近喜歡上富邦悍將的韓國啦啦隊南珉貞,也是個健身後容易跑去吃麥當勞,意志力有夠不堅定的魯蛇。這邊會記錄平常的閱讀心得。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
你可能也想看
Google News 追蹤
Thumbnail
/ 大家現在出門買東西還會帶錢包嗎 鴨鴨發現自己好像快一個禮拜沒帶錢包出門 還是可以天天買滿買好回家(? 因此為了記錄手機消費跟各種紅利優惠 鴨鴨都會特別注意銀行的App好不好用! 像是介面設計就是會很在意的地方 很多銀行通常會為了要滿足不同客群 會推出很多App讓使用者下載 每次
Thumbnail
題目敘述: Reverse Bits 給定一個32bit的整數,請逆序翻轉其二進位表達式,輸出翻轉過後的數字。 例如輸入是二進位1010111 逆序翻轉後是 1110101,對應的十進位數值是117 測試範例 Example 1: Input: n = 00000010100101000
此篇文章連結 RAM 與 C語言陣列的關係並提供陣列與for-loop 使用的相關教學 前半段為基本電腦觀念、後半段為實作能力的教學
Thumbnail
本文提供了一個關於模擬法演算法的問題,介紹了操作指令的格式及其解析。透過程式碼模擬每條指令,找出回到根目錄所需的操作次數。本文詳細說明瞭模擬法的複雜度分析,能夠幫助讀者更好地理解這個問題。
Thumbnail
在讀取檔案時,最怕路徑的問題,常常會有路徑錯誤造成的異常報錯。 為了避免諸如此類的問題發生,明白程式的當前目錄與檔案的路徑是很重要的。 可以利用os 模組是 Python 中的一個標準庫,提供了許多與操作系統的功能。 以下是一些常用的 os 模組基本操作及其範例: 1. os.getcwd
我希望自己能一直寫下去 讀過的東西大部分都會忘記,為了讓自己在未來快速回憶已經理解過的東西,現在要再多花點時間把知識內化,用自己的話寫出來 回到課程內容: 1.recursion每呼叫自己一次就會在stack push 1個”activation record”(我先簡稱為空間,這名稱需要背嗎
Thumbnail
當我們在進行影像處理時, 在Python的世界最常聽到的就是OpenCV, 而我們在處理影片時也會想要僅針對某時間段的影片進行處理, 今天我們就來教您如何透過OpenCV來讀取特定的時間區段。 在進入主題之前, 有一些基本概念務必先行建立, 一個影片是由多張圖片組成的, 因此最小單元為一張圖
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給定我們一個n值,要求我們列出從0 ~ n 之間,每個整數有幾個bit1,以陣列的形式返回答案。 例如n=3時 因為 0 = 0b 0 1 = 0b 1 2 = 0b 10 3 = 0b 11 輸出答案為[0, 1, 1, 2] 題目的原文敘述 測試範例 E
Thumbnail
本文將介紹影像的基本操作包括:影像的讀取、顯示、保存,以及一些常見的操作如裁剪、旋轉、縮放等。 語法介紹 讀取影像: cv2.imread函數的參數是影像的檔案路徑。讀取後的影像以NumPy的ndarray形式表示。
Thumbnail
/ 大家現在出門買東西還會帶錢包嗎 鴨鴨發現自己好像快一個禮拜沒帶錢包出門 還是可以天天買滿買好回家(? 因此為了記錄手機消費跟各種紅利優惠 鴨鴨都會特別注意銀行的App好不好用! 像是介面設計就是會很在意的地方 很多銀行通常會為了要滿足不同客群 會推出很多App讓使用者下載 每次
Thumbnail
題目敘述: Reverse Bits 給定一個32bit的整數,請逆序翻轉其二進位表達式,輸出翻轉過後的數字。 例如輸入是二進位1010111 逆序翻轉後是 1110101,對應的十進位數值是117 測試範例 Example 1: Input: n = 00000010100101000
此篇文章連結 RAM 與 C語言陣列的關係並提供陣列與for-loop 使用的相關教學 前半段為基本電腦觀念、後半段為實作能力的教學
Thumbnail
本文提供了一個關於模擬法演算法的問題,介紹了操作指令的格式及其解析。透過程式碼模擬每條指令,找出回到根目錄所需的操作次數。本文詳細說明瞭模擬法的複雜度分析,能夠幫助讀者更好地理解這個問題。
Thumbnail
在讀取檔案時,最怕路徑的問題,常常會有路徑錯誤造成的異常報錯。 為了避免諸如此類的問題發生,明白程式的當前目錄與檔案的路徑是很重要的。 可以利用os 模組是 Python 中的一個標準庫,提供了許多與操作系統的功能。 以下是一些常用的 os 模組基本操作及其範例: 1. os.getcwd
我希望自己能一直寫下去 讀過的東西大部分都會忘記,為了讓自己在未來快速回憶已經理解過的東西,現在要再多花點時間把知識內化,用自己的話寫出來 回到課程內容: 1.recursion每呼叫自己一次就會在stack push 1個”activation record”(我先簡稱為空間,這名稱需要背嗎
Thumbnail
當我們在進行影像處理時, 在Python的世界最常聽到的就是OpenCV, 而我們在處理影片時也會想要僅針對某時間段的影片進行處理, 今天我們就來教您如何透過OpenCV來讀取特定的時間區段。 在進入主題之前, 有一些基本概念務必先行建立, 一個影片是由多張圖片組成的, 因此最小單元為一張圖
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給定我們一個n值,要求我們列出從0 ~ n 之間,每個整數有幾個bit1,以陣列的形式返回答案。 例如n=3時 因為 0 = 0b 0 1 = 0b 1 2 = 0b 10 3 = 0b 11 輸出答案為[0, 1, 1, 2] 題目的原文敘述 測試範例 E
Thumbnail
本文將介紹影像的基本操作包括:影像的讀取、顯示、保存,以及一些常見的操作如裁剪、旋轉、縮放等。 語法介紹 讀取影像: cv2.imread函數的參數是影像的檔案路徑。讀取後的影像以NumPy的ndarray形式表示。