chapter14

hisun9發表於2024-11-05

第一題

問題

首先,編寫一個名為 null.c 的簡單程式,它建立一個指向整數的指標,將其設定為NULL,然後嘗試對其進行釋放記憶體操作。把它編譯成一個名為 null 的可執行檔案。當你執行這個程式時會發生什麼?

自己寫的

img

輸出如下:

img

無任何輸出或錯誤提示。

第二題

問題

接下來,編譯該程式,其中包含符號資訊(使用-g 標誌)。這樣做可以將更多資訊放入可執行檔案中,使偵錯程式可以訪問有關變數名稱等的更多有用資訊。透過輸入 gdb null,在偵錯程式下執行該程式,然後,一旦 gdb 執行,輸入 run。gdb 顯示什麼資訊?

img

補充:

  • gcc 的 -g 標誌

    gcc 的 -g 標誌用於在編譯時生成除錯資訊,這些除錯資訊可以幫助在程式崩潰或出現錯誤時使用偵錯程式(如 gdb)進行除錯。使用 -g 標誌時,編譯器會將符號資訊和原始碼行號等除錯資訊嵌入到可執行檔案中,這樣你就可以更方便地跟蹤程式的執行情況。

    使用方法:

    在使用 gcc 編譯原始碼時,加入 -g 標誌即可:

    gcc -g -o output_program source_file.c
    
    • -g 會在編譯過程中將除錯資訊新增到可執行檔案中。

    • -o output_program 是指定輸出檔案的名稱。

    • source_file.c 是你的原始碼檔案。

    編譯完成後,你可以使用偵錯程式(如 gdb)進行除錯:

    gdb ./output_program
    

    除錯時,偵錯程式會使用 -g 標誌生成的除錯資訊,顯示原始碼、變數值等除錯資訊,幫助定位問題。

  • gcc 的 -o 標誌(小寫的o)

    gcc 的 -o 標誌用於指定輸出檔案的名稱。預設情況下,gcc 編譯器將生成一個名為 a.out 的可執行檔案,但使用 -o 標誌可以自定義輸出檔案的名稱。

    語法:

    gcc [原始檔] -o [輸出檔名]
    
  • gcc 的 -O 標誌(大寫的O)

    gcc 的 -O 標誌用於啟用最佳化,目的是透過編譯器最佳化程式碼來提高程式的執行效率。-O 後面可以跟不同的數字,以表示不同級別的最佳化。最佳化級別的選擇影響編譯過程的時間、生成的程式碼的執行效率以及可執行檔案的大小。

    -O 標誌的使用:

    • -O0(預設值):不進行最佳化。

      這是編譯器的預設行為,適用於除錯階段,編譯速度快,生成的程式碼沒有經過最佳化,便於除錯。

      gcc -O0 source_file.c -o output_program
      
    • -O1:進行基本最佳化。

      啟用一些基本的最佳化,提升程式的效能,同時不會顯著增加編譯時間。適用於一般情況下的最佳化。

      gcc -O1 source_file.c -o output_program
      
    • -O2:進行更高階的最佳化。

      啟用更強的最佳化手段(例如刪除不必要的程式碼、最佳化迴圈等),可以顯著提高程式的執行效率,編譯時間會相對較長。

      gcc -O2 source_file.c -o output_program
      
    • -O3:進行最大化最佳化。

      啟用所有 -O2 的最佳化,並進一步進行更多的最佳化,目的是提高程式的效能,適用於對效能要求極高的程式。編譯時間和生成的程式碼大小都會增加。

      gcc -O3 source_file.c -o output_program
      
    • -Os:最佳化程式碼大小。

      進行最佳化以減少程式的大小,而不是最大化執行速度。這在記憶體受限的環境中非常有用(例如嵌入式系統)。

      gcc -Os source_file.c -o output_program
      
    • -Ofast:進行快速最佳化。

      啟用所有的 -O3 最佳化,並且還啟用一些可能不符合標準的最佳化,進一步提高程式的執行速度。

      gcc -Ofast source_file.c -o output_program
      

    總結:

    • -O0:不進行最佳化,適合除錯。

    • -O1:基本最佳化,編譯速度較快。

    • -O2:更強的最佳化,適合大多數應用。

    • -O3:最大化最佳化,提升效能但增加編譯時間和檔案大小。

    • -Os:最佳化程式大小,適用於記憶體限制的環境。

    • -Ofast:最大化效能最佳化,適合對速度要求極高的情況。

注意

在編譯的時候一定不要寫成這樣子:

gcc -o -g null_once null.c

否則會報如下錯誤:

img

第三題

問題

最後,對這個程式使用 valgrind 工具。我們將使用屬於 valgrind 的 memcheck 工具來分析發生的情況。輸入以下命令來執行程式:valgrind --leak-check=yes null。當你執行它時會發生什麼?你能解釋工具的輸出嗎?

img

補充

如果是在 centos 下安裝 valgrind 可以看這篇教程 valgrind安裝及使用

然後重中之重,安裝的時候在root使用者下安裝,避免許可權問題!!!

第四題

問題

編寫一個使用 malloc()來分配記憶體的簡單程式,但在退出之前忘記釋放它。這個程式執行時會發生什麼?你可以用 gdb 來查詢它的任何問題嗎?用 valgrind 呢(再次使用
--leak-check=yes 標誌)?

img

使用 gdb:

img

沒看出問題

使用 valgrind:

img

檢測出了記憶體洩漏問題。

第五題

問題

編寫一個程式,使用 malloc 建立一個名為 data、大小為 100 的整數陣列。然後,將data[100]設定為 0。當你執行這個程式時會發生什麼?當你使用 valgrind 執行這個程式時會發生什麼?程式是否正確?

img

使用 gdb:

img

沒看出問題

使用 valgrind:

img

檢測出了無效寫入錯誤。

第六題

問題

建立一個分配整數陣列的程式(如上所述),釋放它們,然後嘗試列印陣列中某個元素的值。程式會執行嗎?當你使用 valgrind 時會發生什麼?

img

輸出了陣列中指定的元素的值

使用 valgrind:

img
img

檢測出了三個無效讀取

第七題

問題

現在傳遞一個有趣的值來釋放(例如,在上面分配的陣列中間的一個指標)。會發生什麼?你是否需要工具來找到這種型別的問題?

img

img

直接報錯,不需要工具就能找到這個問題。

補充

為什麼會報錯?

free(p + 20) 報錯的原因是 free 只能釋放 malloc 分配的原始指標,而不能釋放偏移後的指標。

第八題

問題

嘗試一些其他介面來分配記憶體。例如,建立一個簡單的向量似的資料結構,以及使用 realloc()來管理向量的相關函式。使用陣列來儲存向量元素。當使用者在向量中新增條目時,請使用realloc()為其分配本多空間。這樣的向量表現如何?它與連結串列相比如何?使用valgrind來幫助你發現錯誤。

這就是類似C++中vector的資料結構。 特性類似陣列,可以實現O(1)的訪問或者尾部新增,但是如果空間滿了需要realloc的時候需要O(n)的時間。