1 00:00:00,000 --> 00:00:02,000 [Powered by Google Translate] [Valgrind的] 2 00:00:02,000 --> 00:00:05,000 [內特 - 哈迪森,哈佛大學] 3 00:00:05,000 --> 00:00:07,000 這是CS50,CS50.TV] 4 00:00:07,000 --> 00:00:10,000 在C程序中最困難的錯誤 5 00:00:10,000 --> 00:00:13,000 來自管理不善的內存。 6 00:00:13,000 --> 00:00:15,000 有數量龐大的方式來搞砸了, 7 00:00:15,000 --> 00:00:17,000 包括分配錯誤的內存量, 8 00:00:17,000 --> 00:00:20,000 忘記初始化變量, 9 00:00:20,000 --> 00:00:23,000 寫入結束之前或之後,緩衝液, 10 00:00:23,000 --> 00:00:25,000 和釋放內存保持多次。 11 00:00:25,000 --> 00:00:28,000 症狀的範圍從間歇性崩潰 12 00:00:28,000 --> 00:00:30,000 神不知鬼不覺覆蓋值, 13 00:00:30,000 --> 00:00:34,000 往往是在遠離原來的錯誤的地點和時間。 14 00:00:34,000 --> 00:00:37,000 追踪觀察到的問題的根本原因 15 00:00:37,000 --> 00:00:39,000 可以是具有挑戰性的, 16 00:00:39,000 --> 00:00:42,000 但幸運的是有一個有用的程序,稱為Valgrind的 17 00:00:42,000 --> 00:00:44,000 ,可以做很多事情來幫助。 18 00:00:44,000 --> 00:00:47,000 >> 您在Valgrind下運行的程序,使 19 00:00:47,000 --> 00:00:50,000 堆內存分配和訪問的廣泛的檢查。 20 00:00:50,000 --> 00:00:53,000 Valgrind的檢測到問題時,它可以讓你即時, 21 00:00:53,000 --> 00:00:56,000 直接的信息,讓您 22 00:00:56,000 --> 00:00:58,000 更容易地找到和解決問題。 23 00:00:58,000 --> 00:01:01,000 在Valgrind也不太致命的內存問題的報告, 24 00:01:01,000 --> 00:01:04,000 如內存洩漏,堆內存分配, 25 00:01:04,000 --> 00:01:07,000 忘記釋放它。 26 00:01:07,000 --> 00:01:10,000 像我們的編譯器,鐺,在我們的調試器,GDB, 27 00:01:10,000 --> 00:01:14,000 Valgrind是,它是免費軟件,安裝在設備上。 28 00:01:14,000 --> 00:01:16,000 Valgrind的上運行的二進制可執行文件, 29 00:01:16,000 --> 00:01:20,000 不是你的C或H源代碼文件, 30 00:01:20,000 --> 00:01:23,000 所以要確保你已經編譯到你的程序的最新副本 31 00:01:23,000 --> 00:01:25,000 鐺或製作。 32 00:01:25,000 --> 00:01:28,000 然後,在Valgrind下運行您的程序可以 33 00:01:28,000 --> 00:01:32,000 作為,只是前綴的標準程序命令字Valgrind的簡單, 34 00:01:32,000 --> 00:01:35,000 啟動Valgrind和它內部的運行程序。 35 00:01:35,000 --> 00:01:38,000 當啟動時,Valgrind做一些複雜的 36 00:01:38,000 --> 00:01:41,000 拉坯配置的內存檢查的可執行文件, 37 00:01:41,000 --> 00:01:44,000 因此,它可以採取一個位來啟動和運行。 38 00:01:44,000 --> 00:01:48,000 然後程序將正常執行,無論是速度要慢得多, 39 00:01:48,000 --> 00:01:52,000 當它完成時,Valgrind會打印出其內存使用的摘要。 40 00:01:52,000 --> 00:01:58,000 如果一切順利的話,它會是這個樣子: 41 00:01:58,000 --> 00:02:01,000 在這種情況下,。/ clean_program 42 00:02:01,000 --> 00:02:04,000 我想運行的程序的路徑。 43 00:02:04,000 --> 00:02:06,000 而這一個不帶任何參數, 44 00:02:06,000 --> 00:02:09,000 如果它這樣做,我只是粘性他們像往常一樣到最後的命令。 45 00:02:09,000 --> 00:02:12,000 清潔程序僅僅是一個愚蠢的小程序,我 46 00:02:12,000 --> 00:02:15,000 的整數塊在堆中分配空間, 47 00:02:15,000 --> 00:02:19,000 放一些數值,並釋放內的整個塊。 48 00:02:19,000 --> 00:02:23,000 這是你拍攝的,沒有錯誤,沒有洩漏。 49 00:02:23,000 --> 00:02:27,000 >> 另一個重要的指標是分配的字節數。 50 00:02:27,000 --> 00:02:32,000 根據程序,如果你分配在MB或​​更高, 51 00:02:32,000 --> 00:02:34,000 你可能做錯了什麼。 52 00:02:34,000 --> 00:02:37,000 你不必要的存儲重複? 53 00:02:37,000 --> 00:02:40,000 您是否使用的堆的存儲時,它會更好地使用堆棧? 54 00:02:40,000 --> 00:02:43,000 因此,內存錯誤可能是真正的邪惡。 55 00:02:43,000 --> 00:02:46,000 更加明顯的導致壯觀的崩潰, 56 00:02:46,000 --> 00:02:49,000 但即便如此,它仍然是很難確定 57 00:02:49,000 --> 00:02:51,000 究竟是什麼導致系統崩潰。 58 00:02:51,000 --> 00:02:54,000 更陰險的是,一個程序,一個內存錯誤 59 00:02:54,000 --> 00:02:56,000 仍然可以編譯 60 00:02:56,000 --> 00:02:58,000 似乎仍然可以正常工作 61 00:02:58,000 --> 00:03:01,000 因為你得到幸運的大部分時間。 62 00:03:01,000 --> 00:03:04,000 經過多次“成功的結果,” 63 00:03:04,000 --> 00:03:07,000 你可能認為,事故是僥倖的計算機, 64 00:03:07,000 --> 00:03:10,000 但電腦是永遠不會犯錯。 65 00:03:10,000 --> 00:03:13,000 >> 運行Valgrind的可以幫助你追踪可見的內存錯誤的原因 66 00:03:13,000 --> 00:03:18,000 以及潛伏的錯誤,你甚至不知道的問題。 67 00:03:18,000 --> 00:03:22,000 每次Valgrind的檢測到問題時,它打印信息觀察。 68 00:03:22,000 --> 00:03:24,000 每一個項目是相當簡潔 - 69 00:03:24,000 --> 00:03:27,000 違規指令的源代碼行,是什麼問題, 70 00:03:27,000 --> 00:03:30,000 和一點點的信息所涉及的內存 - 71 00:03:30,000 --> 00:03:34,000 但往往是足夠的信息來將你的注意力到正確的位置。 72 00:03:34,000 --> 00:03:37,000 下面是一個例子,Valgrind的運行在一個錯誤的程序 73 00:03:37,000 --> 00:03:40,000 做一個無效的讀取的堆內存。 74 00:03:40,000 --> 00:03:49,000 我們看到,在編譯沒有錯誤或警告。 75 00:03:49,000 --> 00:03:53,000 嗯,哦,說有兩個錯誤錯誤摘要 - 76 00:03:53,000 --> 00:03:56,000 兩個無效的讀取大小為4 - 字節,也就是。 77 00:03:56,000 --> 00:04:01,000 這兩種壞的讀取發生在主函數中的invalid_read.c, 78 00:04:01,000 --> 00:04:04,000 首先在第16行和第19行的第二個。 79 00:04:04,000 --> 00:04:06,000 讓我們來看看在代碼中。 80 00:04:06,000 --> 00:04:11,000 看起來像調用printf試圖讀取一個int過去的結束我們的內存塊。 81 00:04:11,000 --> 00:04:13,000 如果我們回頭看Valgrind的輸出, 82 00:04:13,000 --> 00:04:16,000 我們看到,正是Valgrind的告訴我們。 83 00:04:16,000 --> 00:04:19,000 地址,我們嘗試讀取0字節開始 84 00:04:19,000 --> 00:04:22,000 過去的結束的塊的大小為16個字節 - 85 00:04:22,000 --> 00:04:25,000 4個32位的int值分配。 86 00:04:25,000 --> 00:04:29,000 也就是說,我們試圖讀取的地址塊結束時,我們的開始, 87 00:04:29,000 --> 00:04:32,000 正如我們看到在我們的壞printf調用。 88 00:04:32,000 --> 00:04:36,000 現在,無效的讀操作可能看起來不是那麼大的交易, 89 00:04:36,000 --> 00:04:39,000 但如果您使用的這些數據來控制你的程序的流量 - 90 00:04:39,000 --> 00:04:42,000 例如,語句或循環的一部分,如果 - 91 00:04:42,000 --> 00:04:45,000 接下來的事情可以靜靜地走壞。 92 00:04:45,000 --> 00:04:47,000 觀看如何我可以運行invalid_read的程序 93 00:04:47,000 --> 00:04:50,000 並沒有什麼不尋常的發生。 94 00:04:50,000 --> 00:04:52,000 可怕的,是吧? 95 00:04:52,000 --> 00:04:56,000 >> 現在,讓我們來看看一些更種在你的代碼中的錯誤,你可能會遇到的, 96 00:04:56,000 --> 00:04:59,000 我們會看到它們是如何Valgrind的檢測。 97 00:04:59,000 --> 00:05:01,000 我們剛剛看到一個例子,一個invalid_read, 98 00:05:01,000 --> 00:05:04,000 所以現在,讓我們看看一個invalid_write。 99 00:05:04,000 --> 00:05:09,000 同樣,沒有編譯錯誤或警告。 100 00:05:09,000 --> 00:05:12,000 好了,Valgrind的說,在這個程序有兩個錯誤 - 101 00:05:12,000 --> 00:05:15,000 和invalid_write和invalid_read。 102 00:05:15,000 --> 00:05:18,000 讓我們來看看這段代碼。 103 00:05:18,000 --> 00:05:21,000 看起來我們已經有了一個實例,經典的strlen加一個錯誤。 104 00:05:21,000 --> 00:05:24,000 該代碼不malloc的一個額外的字節的空間 105 00:05:24,000 --> 00:05:26,000 / 0個字符, 106 00:05:26,000 --> 00:05:30,000 所以,當STR複製去把它寫在ssubstrlen“CS50岩石!” 107 00:05:30,000 --> 00:05:33,000 過去我們的塊寫1個字節。 108 00:05:33,000 --> 00:05:36,000 該invalid_read時,我們使我們調用printf。 109 00:05:36,000 --> 00:05:40,000 printf的閱讀無效的內存時,它會讀取/ 0個字符 110 00:05:40,000 --> 00:05:43,000 因為它看起來在這個E弦的印刷。 111 00:05:43,000 --> 00:05:45,000 但沒有逃脫Valgrind的。 112 00:05:45,000 --> 00:05:48,000 我們可以看到,它抓住了invalid_write的STR副本 113 00:05:48,000 --> 00:05:51,000 在第11行的主,和invalid_read的printf。 114 00:05:51,000 --> 00:05:54,000 岩石上時,Valgrind。 115 00:05:54,000 --> 00:05:57,000 同樣,這可能似乎不是什麼大不了的。 116 00:05:57,000 --> 00:06:00,000 一遍又一遍以外的Valgrind的,我們可以運行這個程序 117 00:06:00,000 --> 00:06:03,000 並沒有看到任何錯誤症狀。 118 00:06:03,000 --> 00:06:06,000 >> 然而,讓我們來看看在這方面的一個細微的變化看 119 00:06:06,000 --> 00:06:09,000 如何可以得到非常糟糕的。 120 00:06:09,000 --> 00:06:14,000 所以,理所當然的,我們是在濫用事情變得更不僅僅是一個位在這段代碼中。 121 00:06:14,000 --> 00:06:17,000 我們只在堆上分配空間的兩個字符串 122 00:06:17,000 --> 00:06:19,000 長度的CS50岩石, 123 00:06:19,000 --> 00:06:22,000 這個時候,記住/ 0字符。 124 00:06:22,000 --> 00:06:25,000 但後​​來我們扔在一個超長字符串的內存塊 125 00:06:25,000 --> 00:06:27,000 S是指向。 126 00:06:27,000 --> 00:06:30,000 什麼樣的影響會有多大的內存塊的T點? 127 00:06:30,000 --> 00:06:34,000 那麼,如果T指向的內存就到S相鄰, 128 00:06:34,000 --> 00:06:37,000 後, 129 00:06:37,000 --> 00:06:39,000 然後我們可能已經寫了一部分,T. 130 00:06:39,000 --> 00:06:41,000 讓我們運行此代碼。 131 00:06:41,000 --> 00:06:43,000 看看發生了什麼事。 132 00:06:43,000 --> 00:06:47,000 在我們的堆塊存儲的字符串,我們似乎已經正確地打印出來。 133 00:06:47,000 --> 00:06:49,000 似乎沒有錯。 134 00:06:49,000 --> 00:06:52,000 然而,讓我們重新回到我們的代碼和 135 00:06:52,000 --> 00:06:55,000 註釋掉該行複製CS50岩石 136 00:06:55,000 --> 00:06:59,000 到第二存儲器塊,指出由t。 137 00:06:59,000 --> 00:07:02,000 現在,當我們運行此代碼時,我們應該 138 00:07:02,000 --> 00:07:06,000 只看到第一個內存塊的內容打印出來。 139 00:07:06,000 --> 00:07:09,000 哇,即使我們沒有STR副本 140 00:07:09,000 --> 00:07:12,000 任何字符到第二堆塊,一個由T, 141 00:07:12,000 --> 00:07:15,000 我們得到了一個打印出來。 142 00:07:15,000 --> 00:07:18,000 事實上,字符串,我們塞進我們的第一個塊 143 00:07:18,000 --> 00:07:21,000 衝出第一數據塊和到所述第二塊, 144 00:07:21,000 --> 00:07:23,000 一切似乎正常。 145 00:07:23,000 --> 00:07:26,000 Valgrind的,儘管告訴我們真實的故事。 146 00:07:26,000 --> 00:07:28,000 我們走吧。 147 00:07:28,000 --> 00:07:32,000 所有那些無效的讀取和寫入。 148 00:07:32,000 --> 00:07:36,000 >> 讓我們來看看另一種錯誤的一個例子。 149 00:07:36,000 --> 00:07:39,000 在這裡,我們做的東西,而不幸的。 150 00:07:39,000 --> 00:07:41,000 我們抓住一個int的堆空間, 151 00:07:41,000 --> 00:07:45,000 我們初始化一個int - P - 指針指向該空間。 152 00:07:45,000 --> 00:07:48,000 然而,當我們的指針被初始化, 153 00:07:48,000 --> 00:07:52,000 的數據,它指向的只是垃圾是在這部分的堆。 154 00:07:52,000 --> 00:07:55,000 所以,當我們加載的數據轉換成int我, 155 00:07:55,000 --> 00:07:57,000 我們在技術上初始化i, 156 00:07:57,000 --> 00:08:00,000 但我們這樣做,用垃圾數據。 157 00:08:00,000 --> 00:08:03,000 調用斷言,這是一個方便的調試宏 158 00:08:03,000 --> 00:08:06,000 適當命名的斷言庫中定義的, 159 00:08:06,000 --> 00:08:09,000 將中止該程序,如果它的測試條件失敗。 160 00:08:09,000 --> 00:08:11,000 也就是說,如果不為0,i是。 161 00:08:11,000 --> 00:08:14,000 根據是什麼的堆空間,p指向的, 162 00:08:14,000 --> 00:08:18,000 這個程序可能會工作,有時並未能在其他時間。 163 00:08:18,000 --> 00:08:20,000 如果一切正常,我們只是幸運。 164 00:08:20,000 --> 00:08:24,000 編譯器不會捕獲這個錯誤,但Valgrind的肯定會。 165 00:08:24,000 --> 00:08:28,000 在那裡,我們看到從我們使用的垃圾數據所產生的誤差。 166 00:08:28,000 --> 00:08:32,000 >> 當您分配的堆內存,但不釋放或釋放, 167 00:08:32,000 --> 00:08:34,000 被稱為洩漏。 168 00:08:34,000 --> 00:08:37,000 對於一個小的,短命的運行的程序,並立即退出, 169 00:08:37,000 --> 00:08:39,000 洩漏是相當無害的, 170 00:08:39,000 --> 00:08:42,000 但對於較大的尺寸和/或壽命的項目, 171 00:08:42,000 --> 00:08:46,000 即使是很小的洩漏可以複合成一些重大。 172 00:08:46,000 --> 00:08:49,000 CS50,我們希望你 173 00:08:49,000 --> 00:08:51,000 照顧釋放所有你分配的堆內存, 174 00:08:51,000 --> 00:08:54,000 因為我們希望您建立的技能,要妥善處理好手工工藝 175 00:08:54,000 --> 00:08:56,000 所要求的C. 176 00:08:56,000 --> 00:08:59,000 要做到這一點,你的程序應該有一個確切的 177 00:08:59,000 --> 00:09:03,000 一對malloc和free調用之間的對應關係。 178 00:09:03,000 --> 00:09:06,000 幸運的是,Valgrind可以幫助你的內存洩漏。 179 00:09:06,000 --> 00:09:09,000 這裡是有漏洞的程序,稱為leak.c分配 180 00:09:09,000 --> 00:09:13,000 在堆上的空間,寫入,但不將其釋放。 181 00:09:13,000 --> 00:09:16,000 我們編譯它的品牌和在Valgrind下運行, 182 00:09:16,000 --> 00:09:18,000 我們可以看到,雖然我們沒有內存不足的錯誤, 183 00:09:18,000 --> 00:09:20,000 我們確實有一個洩漏。 184 00:09:20,000 --> 00:09:23,000 有16字節絕對丟失, 185 00:09:23,000 --> 00:09:27,000 這意味著該內存的指針是不在範圍之內,當程序退出。 186 00:09:27,000 --> 00:09:30,000 現在,Valgrind的不給我們的信息洩漏一噸, 187 00:09:30,000 --> 00:09:35,000 但如果我們按照這個稍微注意一下,它提供了對底部的報告 188 00:09:35,000 --> 00:09:38,000 重新運行 - 洩漏檢查全 189 00:09:38,000 --> 00:09:41,000 洩漏的內存的全部細節, 190 00:09:41,000 --> 00:09:44,000 我們會得到更多的信息。 191 00:09:44,000 --> 00:09:46,000 ,在堆中摘要, 192 00:09:46,000 --> 00:09:50,000 Valgrind的告訴我們,失去了最初分配的內存。 193 00:09:50,000 --> 00:09:52,000 正如我們知道在源代碼中, 194 00:09:52,000 --> 00:09:55,000 Valgrind的告訴我們,我們的內存洩露 195 00:09:55,000 --> 00:09:58,000 leak.c的第8行上調用malloc分配 196 00:09:58,000 --> 00:10:00,000 在主函數中。 197 00:10:00,000 --> 00:10:02,000 相當漂亮的。 198 00:10:02,000 --> 00:10:04,000 >> Valgrind的洩漏使用這些術語進行分類: 199 00:10:04,000 --> 00:10:07,000 絕對迷路 - 這是堆分配內存 200 00:10:07,000 --> 00:10:10,000 程序不再具有一個指針。 201 00:10:10,000 --> 00:10:14,000 Valgrind的都知道,你曾經有過的指針,但已失去了它的軌道。 202 00:10:14,000 --> 00:10:17,000 絕對是這個內存洩露。 203 00:10:17,000 --> 00:10:20,000 間接失去了 - 這是堆分配內存 204 00:10:20,000 --> 00:10:24,000 其中唯一的指針,它也被丟失。 205 00:10:24,000 --> 00:10:27,000 例如,如果你失去了你一個鍊錶的第一個節點的指針, 206 00:10:27,000 --> 00:10:30,000 然後第一個節點本身肯定會被丟失, 207 00:10:30,000 --> 00:10:34,000 而會間接失去了任何後續節點。 208 00:10:34,000 --> 00:10:37,000 可能丟失 - 這是堆分配內存 209 00:10:37,000 --> 00:10:41,000 Valgrind可以無法確定是否有一個指針或不。 210 00:10:41,000 --> 00:10:44,000 仍可達堆分配內存 211 00:10:44,000 --> 00:10:47,000 該程序仍然在出口處有一個指針, 212 00:10:47,000 --> 00:10:50,000 這通常意味著一個全局變量指向它。 213 00:10:50,000 --> 00:10:53,000 要檢查這些洩漏的,你還必須包括選項 214 00:10:53,000 --> 00:10:55,000 - 仍可達= 215 00:10:55,000 --> 00:10:58,000 在你調用Valgrind的。 216 00:10:58,000 --> 00:11:01,000 >> 這些不同的情況下,可能需要不同的策略來清理它們, 217 00:11:01,000 --> 00:11:05,000 但洩漏應該被淘汰。 218 00:11:05,000 --> 00:11:08,000 不幸的是,固定洩漏是很難做到的, 219 00:11:08,000 --> 00:11:11,000 因為不正確的電話免費的,可以吹你的程序。 220 00:11:11,000 --> 00:11:14,000 例如,如果我們看invalid_free.c,, 221 00:11:14,000 --> 00:11:18,000 我們看到壞的內存釋放的一個例子。 222 00:11:18,000 --> 00:11:21,000 什麼應該是一個單一的通話,釋放的整個塊 223 00:11:21,000 --> 00:11:24,000 的內存的int_block, 224 00:11:24,000 --> 00:11:27,000 如今卻成為釋放每一個int大小的部分 225 00:11:27,000 --> 00:11:29,000 單獨的存儲器。 226 00:11:29,000 --> 00:11:32,000 這將失敗災難性的。 227 00:11:32,000 --> 00:11:34,000 轟!什麼是錯誤。 228 00:11:34,000 --> 00:11:36,000 這肯定是不好的。 229 00:11:36,000 --> 00:11:39,000 如果你堅持這樣的錯誤,雖然,你不知道去哪裡找, 230 00:11:39,000 --> 00:11:41,000 回到屬於你最好的朋友。 231 00:11:41,000 --> 00:11:44,000 你猜對了 - Valgrind的。 232 00:11:44,000 --> 00:11:47,000 Valgrind的,總是知道到底是什麼了。 233 00:11:47,000 --> 00:11:50,000 alloc和自由的計數不匹配。 234 00:11:50,000 --> 00:11:52,000 我們已經拿到了1 alloc和釋放。 235 00:11:52,000 --> 00:11:55,000 和Valgrind也告訴我們,其中第一個壞的免費電話 - 236 00:11:55,000 --> 00:11:58,000 一個觸發的爆破 - 來自 - 237 00:11:58,000 --> 00:12:00,000 線16。 238 00:12:00,000 --> 00:12:03,000 正如你看到的,不好的呼叫釋放是非常糟糕的, 239 00:12:03,000 --> 00:12:05,000 因此,我們建議讓你的程序洩漏 240 00:12:05,000 --> 00:12:08,000 當你的工作獲得正確的功能。 241 00:12:08,000 --> 00:12:12,000 後,才開始尋找洩漏你的程序工作正常, 242 00:12:12,000 --> 00:12:14,000 沒有任何其他的錯誤。 243 00:12:14,000 --> 00:12:16,000 >> 而這一切,我們已經有了這個視頻。 244 00:12:16,000 --> 00:12:18,000 你還等什麼呢? 245 00:12:18,000 --> 00:12:21,000 轉到現在對您的程序運行Valgrind的。 246 00:12:21,000 --> 00:12:25,000 我的名字是Nate哈迪森。這是CS50。 [CS50.TV]