[音乐播放]
好
首先 一段视频 来自一个大家都很熟悉的人
[视频播放]
好
这里是CS50 现在是第三周的开始
很抱歉今天我不能和大家在一起
但请让我给大家介绍 CS50的Rob Boden
[视频播放结束]
[鼓掌与欢呼]
这视频拍得太棒了
好
首先 我们又有一次午餐活动了
定在明天1:15分
本周五将不会有午餐活动
这一次是和Quora的午餐活动
Tommy现在还没来 但是届时参加活动的有这门课的
前核心助教Tommy McWilliam
他是个很有趣的人
你们应该来参加
好
上一周 我们开始分解析字串究竟是什么
我们从一开始就知道 它是字符组成的序列
但上一周 我们发现看起来是字符组成的序列的东西
实际上 是字符数组
所以我们知道 一个字串 其实是字符数组
在它的最后 我们有null字节 也就是\0
来表示字串的结束
所以字串是字符数组 但我们可以不仅有字符数组
我们还可以有
任何东西组成的数组
如果你还记得上周
David演示ages程序
所以首先 我们要做的 是问用户要一个整数
有多少个人在这个房间里
我们有了这个整数之后 我们就声明数组
注意用中括号的这个句法
你们会慢慢习惯它的
所以我们声明了整数数组 叫ages
在这个数组里有n个整数
所以这里的这个模式就 for (int i = 0; i < n; i++)
你们也会习惯这个模式的
因为这就是我们要循环访问数组时要采取的模式
记住 n就是数组的长度
所以这里 我们在重复问i这个人的年龄
然后 我们往下 然后我们决定
要印出他们一年以后的年龄
我们来运行这个程序 make ages, ./ages
屋子里有多少个人 就说三个好了
假设第一个人13岁 第二个26岁 最后一个30岁
然后程序会循环访问这三个人 输出 14， 27 和31
记住 当我们声明大小为n的数组时
数组中的索引值 是从 0,1,2
一直到 n-1
所以当我们说 房间里有3个人 然后第一次循环访问
这个循环时 i等于0
所以在索引0中
我们赋了用户输入的第一个年龄
在下一个索引值中 我们放了用户输入的第二个年龄
在索引值2中 放入最后一个值
所以我们发现 大小为3的数组
并没有索引值3
这是不成立的
好
回到这里
现在我们看了数组 对它有了一定了解
我们下面要看命令行参数
这和这一次的练习比较有关联
所以到目前为止 不管你什么时候声明你的main函数
我们都用的是int main void
void表示我们不会
给这个函数任何参数
现在我们会看到 main也可以接受参数
这里我们叫它 int argc和string argv[]
这里的中括号 表示我们在和数组打交道
所以在这里 string argv[] 我们是在和数组打交道
argc 这会表示
我们给了程序多少个参数
为了解释这是什么意思 让我们把这个关掉
好
到目前为止 我们都用相同的方法运行程序 就像刚才的./ages
我们也可以 在命令行 递参数
也就是这里的这个概念 命令行参数
第一个参数 hello world
这里 argc就会是3
它统计了命令行参数的个数
argc至少是1 因为./ages它本身
就是一个命令行参数
hello就是第一个命令行参数
如果./ages是第0个 那么hello就是第一个 world是
第二个命令行参数
所以string argv 我们会看到 包含了字串
./ages, hello 还有world
根据David的要求 我们要放一段视频介绍一下这个概念
[视频播放]
目前为止 我们写过的程序中
我们都用int main void声明了main
一直以来 void表示的
是程序不接受任何命令行参数
换句话说 当用户运行一个程序时 他或她可以
在提示处的程序名称后面 另外写一些词或词组
来提供命令行参数
如果你想要自己的程序接受命令行参数的话
不管是一个还是多个这样的参数 我们需要用一些参数来取代void
所以让我们行动起来
Include CS50.h
Include standard io.h
Int main
现在 我要做的不是输入void 还是明确一个整数叫argc
以及一个字串的数组 叫argv
argc与argv只是我们习惯这样写
我们也可以给这些参数取随便什么名字
不过重点是 argc是一个整数 因为根据定义
它是参数的个数 也就是用户看到提示后
一共输入了多少个词
argv 也就是argument vector(变元向量)是一个数组
里面储存着所有用户在提示后输入的词
让我们接下来
用这些命令行参数做点什么
准确来说 让我们印出用户在提示处这个程序的名字后面
输入的那个词
{
}
printf %s\n,
现在我要告诉printf要给占位符赋什么值
我想要用户在程序名字后面输入的第一个词
所以我会说
argv[1]);
为什么是[1]而不是[0]呢?
因为我们发现 argv0里会自动保存
程序的名字
所以用户在程序名称后输入的词
按惯例 会被储存 在argv1里
现在让我们编译并运行这个程序
Make argv 0, ./argv 0
现在输入一个词比如hello
回车
我们就得到了 hello
[视频播放结束]
好
关掉这个
所以让我们看一下刚才介绍的程序
只是要演示下 如果我们印出argv 0, make 然后要什么来着 argv 0, ./argv 0
如我们所料 它印出了程序的名字 因为
argv 0永远都是程序的名字
但是让我们来做些更有意思的事
在练习中 你们会认识这个函数 atoi
我们用atoi来做什么呢?
它会把一个字串变成一个整数
如果我把这个字串 比如1 2 3 递给atoi 它会
把字串转换成整数 1 2 3
所以我们要把第一个命令行参数转换成一个整数
然后把这个整数印出去
所以也就是说 我们有点像在重新实现getint
只不过我们在命令行输入了整数
而不是在程序中输入
所以 编译argv 0 让我们在这里做 然后关掉这个
所以运行argv 0 然后让我们输入整数 1 2 3 4 1 2
它会输出整数 1 2 3 4 1 2
atoi有些微妙之处 比如它除了有效数字字符以外
什么都不会管 但这没关系
所以你们觉得我做这个之后会发生什么?
分段错误
为什么呢?
如果你回过头来看我们的程序 我们在把argv 1
在程序名字后的第一个参数 转换成整数
但是程序名字后 并没有任何参数被传递给过
所以这里 我们可以看到这是个有bug的程序 因为
如果你不带参数地想要运行它 它会崩溃
所以你们还会看到的一个为常见模式就是
如果argc小于2 表示 这个程序至少有它的名字
和第一个参数这话是错的 那我们可以用printf
命令行参数不足
印出这个不是个好主意 一般我们都是印出
你应该在命令行输入一个整数 什么的
放一个n在这里
然后回传1
记住 在我们程序最后 如果我们回传了0
就代表我们成功了
而且main会自动回传0 如果你没有这么做的话
所以这里 我们回传了1 表示没有成功
你可以随便回传任何东西 不过 0代表成功
其他任何东西都表示失败
所以让我们运行一下这个版本
现在 如果我们没有输入命令行参数
它就会告诉我们 命令行参数不足
你这句不完整
否则 如果我们递给它一个参数 它就能让程序完整
你就可以这样用argc来确定有多少
命令行参数被递了过去
让我们把程序再变复杂些
来看一下第二次循环
现在 我们不只是要输出第一个命令行参数了
这里 我们经过了 int i = 0 i < argc, i++
然后印出argv 索引值为i的
这个模式 和先前相同
只不过我们没有用变量n 而是argc
这个会循环经过数组中的每一个索引 然后
把数组中的每一个元素都印出
所以 当你运行这个程序时 我还没有输入任何命令行参数
所以它只会印出程序名字
如果我输入了一堆东西 它会印出它们 每个单独一行
好
让我们再前进一步
我们不想要在每行印出一个参数 而是在每行印出
每一个参数的每一个字符
记住 argv是字串的数组
凹面镜字串 其实是字符数组
这就是说 argv实际上是一个字符数组的数组
我们可以利用这一点 但现在我们先不要管它
让我们只考虑字串 argv 0
如果我们想要把argv 0的每一个字符 都单独在一行中输出
那我们会想要用我们熟悉的模式 i比数组的长度要短
在这里 也就是strlen 这不是我想做的
string s = argv 0
i比我们的数组的长度要小 在这里 也就是字符数组
i++
所以 就像我们上周看到的 最好把strlen移到条件外面
因为这样每次经过循环时 n就会增加s的strlen
而s的长度不会变
所以我们就让它等于这边的n
好的
现在 我们在循环访问数组中的每一个索引
这样 如果我们想要印出数组里的每一个字符
我们应该用%c 作为字符的标记
现在[i]是字串中的第i个字符
所以如果字串是hello
那么s[0]就会是h s[1]是e 以此类推
现在我们想把这两者结合起来
我们想输出每个命令行参数中的每个字符
那我们就得有个嵌套的for循环
照惯例 第一个计数器是i 第二个会是j
n = strlen (argv [i]), i < n, i++
现在 我们不要印出argv i, argv[i]是索引
也就是第i个命令行参数 argv [i][j]
j是第i个参数的第j个字符
在这儿我可以把它去掉了 因为我们已经把它放到了循环里
这等同于 string s = argv i 然后 s[j]
我们不需要声明这个变量s
我们要做的 是把这两个合并成我们有的东西 argv [i][j]
[学生提问]
谢谢指出
这个其实坏了
如果我运行它的话 我们就会发现它坏了
在这个for循环里我在意的计数器
就是j 迭代器
如果我们没有修正这个的话 你可能会碰到问题
比如说一个无限循环
所以我们今天也会讲到怎样debug
好
让我们来运行一下这个程序
让我们在这加上一个printf 它会再印出一行
这意味着当我们运行程序时 在每一个命令行参数的每一个字符间
都会有一个空白行
我们会知道这代表什么的
不好
有些bug
隐式声明函数strlen错误
回到我们的程序 我忘记要包括string.h了
string.h是用来声明
strlen这个函数的头文件
好的 编译成功了
现在我们来运行它
就这样
它会印出我们程序的名字 hello world
它会印出每个东西 也就是一行印出一个字符
好
让我们再进一步
我们不想再用string.h了 我们想怎样才能
实行我们自己的strlen函数
所以我要马上来给一个函数签名
让我叫它my_strlen 然后它会接受一个字串当作参数
我们希望它会回传这个字串的长度
去哪了?
好
还记得前一张幻灯里 也是我们上周讲过的
字符数组 也就是 字串 假如说这是我们的字串s
如果s是字串hello 那么在内存中就是 H-E-L-L-O
加上\0这个字符
那么我们要怎样取得s的长度?
诀窍在于找这个\o
这个用来结尾的空字符
所以这个算法
只会有几个字符
让我们用这只手来表示计数器 让我们叫它int length
从这边开始 我们会循环访问我们的字串
第一个字符 也就是H还没到\0
所以长度是1
循环访问下一个字符 E 也没有\0
长度为2
L 3
L4
O 5
终于 我们找到了\0 这表示
字串结束了
所以让我们回传5
所以在实际过程中 一开始 int length = 0 我的右手
然后我们开始循环访问
[学生提问]
不好
谢谢指出
好了
int length = 0
现在 当s长度不等于\0
记住 \0是一个实际存在的字符
它表示字串的结束
就像 \n也是一个实际存在的字符一样
\0表示字串的结束
我不想把它放在这
当s用长度来索引时 还没有到表示结束的空字符时
我们就加长长度
那么 在这个情况下 在程序最后 长度最终
会变成5
然后我们就会回传长度
好
在底下这里 我也可以这样 my_strlen
让我们先编译一下 保证一下都会顺利运行
我是用2做的么
还是1？
这应该行了
好的
这是argv 2
像我们预计的一样 成功了 这是我刚才改的那个么?
是的
好
这个版本后面没有printf new line
但是它其实是一样的
好
像我们预期的一样成功了
现在我们还可以进一步将这一步合并
注意在这里 首先我们取得了argv的字串长度 然后我们循环访问了
这个字串里的每一个字符
现在我们不要这样做 如果我们沿用这样的逻辑
也就是等到我们达到\0的时候 然后把这个逻辑放到for循环里
所以 循环访问 当argv [i][j]不等于\0
让我们先运行一下
好
这里 这个条件在说
让我们把它擦掉
现在 让这个变成我们的argv
我刚才运行程序的时候 argv是字串的数组
所以 如果我用./argv 2, hello world运行它
那么argv本身长度是3 因为有 argv 0, hello 还有world
在这些索引里面 还有数组
也就是 . / 我不知道这样分是不是正
看来不是
A-R-V - 还需要更多空间
让我们来看一下这个数组
A-R-V-0, 然后是\0
E?然后这个数组会是hello
让我们假设说 H-E\0
最终 W-O\0
我们刚写的这个算法 这个内嵌的for循环
它在做的 就是 我们首先有计数器i然后是j
屏幕上有代码的话这个解释起来会容易点 所以让我们回到这来
好
注意 i是迭代器 它在循环访问
每个命令行参数
j是迭代器循环访问每个命令行参数
里的每一个字符
所以这个最里面的printf在做的 就是 我们有printf argv 0 0
printf argv 0 1, printf argv 0 2, 0 3, 0 4, 0 5, 0 6, 但现在 argv 0 7
会等于\0
然后我们就退出循环 现在i变成1
我们就会printf argv 1 0, argv 1 1
现在 因为我把hello变短了 argv 1 2 又一次
会变成\0
所以 增加i然后继续 就这样 直到我们把world都印出来
然后这些是三个命令行参数
我们要从最外面这个循环里退出 然后结束我们的程序
好
让我们回到这里
在这个练习中
你们会对命令行参数更加熟悉
现在 关于debug
在前一个练习中 你们大概已经需要
去debug了
debug一个非常简单的办法 是 首先 让我们来看个有bug的程序
我们看到这个程序 我们会问用户要一个整数
拿这个整数 然后我们决定放一个while循环
它会减少i直到i等于10
让我们假设 我输入了一个比10大的整数
减少i直到它等于10为止
然后我们还有一个while循环 当i不等于0时
我们就把i减3
所以如果你们发现这个bug在做什么的话 它其实是在
把i减少到10 然后这个while循环会把i从10减少到7,4,1
然后到-2,-5以此类推 到负无穷
因为i永远不会等于0
在这个程序在最后 我们有一个foo函数
它会把i印出来
这只是个很短的程序 但是bug很明显
特别是在我指出bug在哪以后
不过我的目的 是告诉大家 这个可能会和
你们上一个练习采取的解决方案差不多
也许你在程序中确实遇到了无限循环
而你不知道原因是什么
所以一个很有用的debug技巧 就是
在你的代码中加上很多printf
这里 我要放一个printf 印出 在第一个while循环外
这里我要一个printf 印出 i
我可以说 印出 第一个while循环 i
印出 在第二个while循环外
又一次 在这里面印出 第二个循环里 i的值
让我们来运行这个
./debug
输入一个整数
让我们输入13
然后
我们发现我们在第二个while循环里无限循环了
现在我们就知道bug在哪里了
用printf来debug完全没有问题 但是一但你的程序
开始变长变复杂 我们就有更
复杂的方法来解决问题
让我们把这些printf都移掉
让我确认一下我没弄坏什么东西
好
我们要介绍的程序
叫做GDB 也就是GNU侦錯器
事实上 让我们把debug移走一下 然后重新编译debug
不过首先 关于命令行参数
注意这个Clang命令 它在编译所有东西
而它在命令行递出了 这些命令行参数
所以我们前面怎么用命令行参数的
你们在练习2中也会这样用 而Clang也是这样用命令行参数的
注意到第一个标记 -ggdb3 这个的意思是
Clang 你应该编译这个文件 而最终目的
是我们最终会需要debug它
所以只要你有这个标记 我们就可以GDB debug
它会打开GNU侦错器
这边有很多的命令 你们都要熟悉
第一个 你马上就会用到的 是Run
那么Run会做什么呢?
它会运行我们的程序
所以 run 运行程序 这个程序问我们要一个整数 13
然后就是我们预期到的 无限循环 只不过
我把printf移掉了 所以我们不会看见它
正常退出了
哦
有可能它是被包住 -- 我们不管它
假设它没有正常退出
那么就有一个非常复杂的答案了
所以现在 这并没有什么用
所以在这个侦错器里运行我们的程序对我们并没有帮助
因为我们也可以在GDB外用./debug
所以你们可能要用的命令--
让我退出这个
Control-d或退出 都行
让我们把它重新打开
另一个你们想要马上熟悉起来的命令
大概是Break
让我们对main函数用break 然后我来解释一下它
在这里我们看到 我们在debug.c里的这一行里放了一个断点
所以break(断)的意思是 在我输入run时
这个程序会一直运行 直到它遇到断点为止
所以当我运行 程序启动后 它在进入
main函数时就会断
break main将会是你们会经常做的事
现在 给你们介绍更多的一些命令
注意在这里 它说我们在第11行断了
那里是printf 输入一个整数
Next 这个命令会告诉我们到下一行代码去
它会让我们一行一行地过我们的程序
所以 next
现在 在第12行 我们会得到整数
next
如果你再敲一下回车 它会重复一次你最后做的事
所以我不用每次都输入 next
输入一个整数 13
现在 在第14行 当i比10大时 我来用next
我们发现我会减少i
所以我们会再一次减少i
所以现在 另一个有用的命令是Print
Print会印出变量的值
那让我们把变量i的值拿出来
让我们来print i
它会说 i是11
现在我们再来用next 然后 当i比10大时
所以 i仍然比10大 因为它是11
i--
让我们再print i
像我们预计得那样 i是10
再 next
回到条件 i比10大 但现在 i是10
所以它没有比10大 所以我们希望它会从while循环中退出
现在我们到了那行代码下面
另外一个命令 List 会列出前后的几行代码
以免你不知道自己在程序的什么地方了
所以我们刚退出了这个while循环 现在我们进入了
这个while循环 第18行的
当i不等于0
下一行 i等于i-3 我们会发现 这会一直运行
我们可以印出i
每一个命令都有所谓的快捷方式
p是Print的缩写
所以我们可以 p i
一直按住n或者一直输入next都行
再一次Print i
可以看到 它现在是－167了
所以这个会一直进行下去 但不是真的 一直进行 因为你们刚也看见了
它在某一点是会停止的
这就是GDB的简单教程
但让我们在GDB中再做一件事
debug
所以 在这个情况下 这个无限循环
其实是在main函数里的
现在 只要知道我要把这个无限循环移到
foo函数里就可以了
只要记住 在这个程序最后 原本我们在调用foo
它的作用就是印出i
但现在我们调用foo 它会减少i直到它是0
然后再印出这个变量
好
保存
make debug
现在 gdb debug
好
所以如果我只输入Run 那我就不能一行行地
看我的程序了
所以让我们在main这里用break 然后输入run
让我们过一遍 printf 输入一个整数 取得整数 13
我们会一直减少i 直到i不大于10
然后我们会进入while循环 然后到那一行--
让我们在新窗口里打开它
所以我们减少i直到它不再大于10
然后我们调用函数foo
当我到foo函数这边的时候 我调用了foo
这时我就不再对GDB有控制权了
所以当我在这行输入Next时 它会继续运行
直到这里 程序退出了--
假设它最终没有退出
不过 你们看到它暂停了一下
所以为什么 在这里 我失去了对程序的控制呢
因为当我输入next时 会真的
跳到它将要执行的下一行代码去
所以在第21行后 它将要执行的是第22行代码
也就是 从main中退出了
所以我不想只是去下一行代码
我想要去foo这个函数 然后一行一行地
过它的代码
这样 我们有另一个方法可以处理
让我们再次退出这个
break main
1 next, next, 13, next, next, next, 要小心
在我们到foo这一行之前
好
现在 我们在第21行了 在这里我们会开始用foo
我们不想输入next因为这样就会调用foo函数
就到下一行代码去了
我们想要用的是Step
Step和Next有个区别 Step会让你进入函数
而Next会过一个函数
它会执行整个函数 然后继续
所以Step会把我们带入函数foo中
我们现在可以在这里看到 我们重新回到了这个while循环 理论上来说
它会永远循环下去
如果你按了Step 当没有函数可以调用时
它就和Next一样了
所以只有当你在要调用函数的那一行时
Step才会和Next有区别
所以Step会把我们带到这
Step, step, step, step, step, step 然后我们就会无限循环下去
你们最好熟悉这个 这个判断无限循环的方法
就是按住回车键来看你在哪卡住了
还有更好的方法 但是现在 这样就够用了
从风格上来说 为了去Style 50相统一 我应该这样做的
好
最后介绍一个命令
让我们回到gdb debug
这一次 不是在main函数这断 如果我知道foo函数也有问题
那我就可以说 在foo这里断
假设我在main和foo那都断了
你想建多少个断点就可以建多少个
当我输入run时 它会在--
让我们重新编译下 因为我改了点东西
你们会看到这一行 警告 源文件比执行文件更新
这表示 我刚才在这里做了些改动
使之与Style 50统一 但我没有重新编译程序
所以GDB提醒了我
我要退出 重新make debug, 敲击gdb debug
好
现在 回到我刚才的地方
Break main, break foo
现在如果我运行这个程序 它会一直运行
直到遇到断点为止
断点在main这里
我不想要一直next, next, next, next, next 直到我抵达foo
我可以输入continue 它会让你继续 直到遇到下一个断点
我得先输入整数
Continue会让我们继续 直到我抵达下一个断点
也就是foo函数
所以 Run会让你运行 直到遇到断点 但是你只会在
程序开始时输入run 从那以后 就是continue了
如果我只在main处断 那运行它时
它就会在main处断 然后继续
因为我在foo这里没有断点 输入一个整数
现在我们会在foo这里断
它会进行无限循环
好
这就是对GDB的介绍
在你们的练习中 你们应该开始用它了
它对于确定bug在哪十分有用
如果你能一行一行地过你的代码
然后比对你想要它做的事和实际发生的事
那么基本上就不会错过bug
好
上周David首次提到了这个密码
我们不想把密码以纯文字文件
储存在我们的电脑里 那样别人
很容易过来打开并阅读它们
理想情况下 它们应该被加密
在练习2里 你们会遇到一种加密方法
好吧 是两种 但它们不是太好的方法
如果你们做黑客版的话 你们还需要去
解密一些东西
所以现在的问题是 如果我们有
这世上最强的加密算法 如果你选了个很弱的密码
它并不会帮到你什么 因为人们还是能解出它来
即使加密过的字串看上去像一团乱麻
对他们毫无意义 他们也许试过几次就试出来密码了
所以这就不太安全
让我们来看一段视频 讲的就是这个
[视频播放]
Helmet 你这个恶魔
发生什么了?
你要对我女儿做什么?
请让我为您介绍这位年轻的天才整形师 Phillip Schlotkin 医生
全宇宙鼻子做的最好的
在比弗利山庄也是一样
陛下
鼻子?
我不懂
她已经整过鼻子了
那是我送她的16岁生日礼物
不
不是你想的那样
比那糟糕得多
如果你不给我空气盾的密码
Dr. Schlotkin就会把你女儿的旧鼻子安回去
不
你从哪得到这个的
好吧
我告诉你
我会说的
不爸爸
不 你不能
你说得对 亲爱的
我会想念你的新鼻子的
但我不会告诉他密码的 无论如何都不会
非常好
Dr. Schlotkin 怎么糟怎么来吧
十分乐意
[磨工具声]
不
等下
我告诉你
我会说
我就知道这行得通
好吧
告诉我
密码是1
1
2
3
4
5
所以密码是 1 2 3 4 5
这是我听过的最蠢的密码了
这是哪个蠢蛋的旅行箱密码吧
谢谢您 陛下
你做了什么?
我把墙关了？？
不 你没有
你把整部电影关了
我一定是按错了键
把它重开开来啊
把电影重新开开来
是 先生
是先生
我们走吧 Arnold
Gretchen 来吧
当然 你知道我们是要收你钱的
[视频播放结束]
好
现在他们已经在讲到安全性了
这个电影的海报不错 近来 有关国安局
窃听事件
在网上 感受到有隐私并不是件容易的事
虽然我也不知道"棱镜"计划的具体细节
不说"棱镜"了
现在想一下你的笔记本电脑
在上面这里 我想要换去我真正的帐户
有我的小企鹅的那个
所以我有一组密码 这个密码是我决定的
但记住我是靠什么登录的
这个登录提示 是某种程序
是某个人写的某种程序
所以 这个人 如果他们不怀好意
他们可以说 好 如果我输入的密码
等于我真正的密码 或者等于某种特殊的密码
比如David很棒 什么的
那就让我登入
那么一个不怀好意的程序员 就可以
登录你们所有人的Mac 或者Windows 或者任何东西
但这并不是个问题 因为 这个登录程序
是随OS X过来的 千百个人
都查看过这个代码了
所以 如果在你的代码里 你说 如果这个字串
== David好棒 登录 那么有人会说 等下
这不对
这不应该在这的
这就是我们用来保证安全性的一种方法
但是想想你写的程序
假设你写了这个登录程序
你写了这个登录程序 所以显然
你是个很优秀的程序员
你是不会不怀好意 把什么x==David好棒
放进你的代码里的
但这个程序 你用什么来编译这个程序呢?
某种像Clang的东西
如果那个写Clang的人在Clang中说有这样一种特殊情况
如果我在编译登录程序 那么把这个
如果x==David好棒的代码放入登录程序中
但是我们有同样的问题 Clang也一样
有几千个 甚至几万个人都检查过Clang
看过它的代码然后说 好 这边没有问题
显然 没有人在干坏事
但是假如Clang它本身 比如 如果我编译Clang会怎样?
如果我有某种编译器 能编译Clang 然后往里植入
这个特别的黑客 当我编译clang时
我拿到的执行文件就会在登录程序中
寻找并植入这个密码 ==David好棒
记住 你的编译器 在某个时候 也是要被编译的
如果你用某个恶意程序来编译Clang
那你就会把后面的一切都毁了
所以这里 我们有Ken Thompson和 Dennis Ritchie
这是个标志性的照片
右边的是Dennis Ritchie
他是一名--
C差不多就是他写出来的 所以你们得为了这节课谢谢他
左边的是Ken Thompson
他俩一起差不多写出了UNIX
他们是UNIX的主要开发者
还有些其他人
Ken Thompson在某年得了图灵奖
图灵奖 我一直听人这么比喻
是计算机界的诺贝尔奖
所以在图灵奖上 他要致领奖辞
他的演讲 现在已经很有名了
叫 有关信任信任的思考 我们已经把链接放在课程网站上了
在这个演讲中 他说 好 我写了UNIX
你们所以人都在用UNIX
记住 今天的Linux就是从UNIX中发展出来的
OS X直接会用到UNIX
Windows没用到什么UNIX 但很多概念都来自UNIX
所以他到了台上 说 好 我写了UNIX
就告诉你们一声 我有办法
登入你们所有人的电脑
因为我放了这个特别的 如果x==Ken Thomson好棒
这样我就能登录了
那么人们就问 你是怎么做到的?
我们查了登录程序了 那儿什么都没有
他就说 我改了编译器用它登入登录程序
这样登录程序里就会有x==Ken Thompson好棒E？
这个东西了
然后人们说 这一定不是真的
我们看了编译器 而编译器中
没有这样的代码
他说 好 但是你用什么来编译编译器呢?
然后人们在想思考 他就说 我给了你们用来编译编译器的编译器
所以你们在编译编译器的时候
它本身就是恶意的 所以会破坏登录程序
所以从本质上来说 你没办法去看登录程序的源代码
来看它是不是有问题
你甚至不能去看编译器的源代码
来看有没有问题
你得看机器码 也就是编译后的编译器的二进制码
来看 等下 这行代码不应该在这的
但是Ken Thompson多做了一步 说
有三种特殊的程序 可以帮你读程序的二进制码
所以如果有人用这个程序来读二进制
他们会看见这些代码行
他改变了程序 说 好 如果你在看编译器
不要显示这些二进制码
所以这样你也要更进一步 但是
它可能间接有很多层 所以到了某个时候
就不会有人去查了
这个故事告诉我们
你们不会在这门课上去写Clang的
你们会在这门课中用到很多的Clang
你们所了解到的 就是Clang是一个会损害
你们编译的所有程序的程序
把这句非常不祥的话留给大家 我们周三见
[掌声]
在下一切CS50课上
你敢说这句试试
你可以的
你以前成功过的 你今天也可以的 你明天也会成功的
你已经这么做很多年了
就上来完成它
你可以的
[音乐播放]