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 然后,在V​​algrind下运行您的程序可以 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]