第一題
問題
首先,編寫一個名為 null.c 的簡單程式,它建立一個指向整數的指標,將其設定為NULL,然後嘗試對其進行釋放記憶體操作。把它編譯成一個名為 null 的可執行檔案。當你執行這個程式時會發生什麼?
自己寫的
輸出如下:
無任何輸出或錯誤提示。
第二題
問題
接下來,編譯該程式,其中包含符號資訊(使用-g 標誌)。這樣做可以將更多資訊放入可執行檔案中,使偵錯程式可以訪問有關變數名稱等的更多有用資訊。透過輸入 gdb null,在偵錯程式下執行該程式,然後,一旦 gdb 執行,輸入 run。gdb 顯示什麼資訊?
補充:
-
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
否則會報如下錯誤:
第三題
問題
最後,對這個程式使用 valgrind 工具。我們將使用屬於 valgrind 的 memcheck 工具來分析發生的情況。輸入以下命令來執行程式:valgrind --leak-check=yes null。當你執行它時會發生什麼?你能解釋工具的輸出嗎?
補充
如果是在 centos 下安裝 valgrind 可以看這篇教程 valgrind安裝及使用
然後重中之重,安裝的時候在root使用者下安裝,避免許可權問題!!!
第四題
問題
編寫一個使用 malloc()來分配記憶體的簡單程式,但在退出之前忘記釋放它。這個程式執行時會發生什麼?你可以用 gdb 來查詢它的任何問題嗎?用 valgrind 呢(再次使用
--leak-check=yes 標誌)?
使用 gdb:
沒看出問題
使用 valgrind:
檢測出了記憶體洩漏問題。
第五題
問題
編寫一個程式,使用 malloc 建立一個名為 data、大小為 100 的整數陣列。然後,將data[100]設定為 0。當你執行這個程式時會發生什麼?當你使用 valgrind 執行這個程式時會發生什麼?程式是否正確?
使用 gdb:
沒看出問題
使用 valgrind:
檢測出了無效寫入錯誤。
第六題
問題
建立一個分配整數陣列的程式(如上所述),釋放它們,然後嘗試列印陣列中某個元素的值。程式會執行嗎?當你使用 valgrind 時會發生什麼?
輸出了陣列中指定的元素的值
使用 valgrind:
檢測出了三個無效讀取
第七題
問題
現在傳遞一個有趣的值來釋放(例如,在上面分配的陣列中間的一個指標)。會發生什麼?你是否需要工具來找到這種型別的問題?
直接報錯,不需要工具就能找到這個問題。
補充
為什麼會報錯?
free(p + 20)
報錯的原因是 free 只能釋放 malloc 分配的原始指標,而不能釋放偏移後的指標。
第八題
問題
嘗試一些其他介面來分配記憶體。例如,建立一個簡單的向量似的資料結構,以及使用 realloc()來管理向量的相關函式。使用陣列來儲存向量元素。當使用者在向量中新增條目時,請使用realloc()為其分配本多空間。這樣的向量表現如何?它與連結串列相比如何?使用valgrind來幫助你發現錯誤。
這就是類似C++中vector的資料結構。 特性類似陣列,可以實現O(1)的訪問或者尾部新增,但是如果空間滿了需要realloc的時候需要O(n)的時間。