0%

一、Makefile

这个 Makefile 要比之前的文件夹中的 Makefile 更加复杂,是因为之前的文件夹都是对操作系统特定部分的一个编译指导,所以基本上是实现的功能就是“对应的 C 文件和汇编文件编译成目标文件”这一个功能,最后合成一个整体。但是 user 的 Makefile 指导的是多个用户程序的编译,最后生成的是多个用户目标文件,同时还需要给每个用户文件装备上库目标文件。

首先先补充一下 makefile 的一些知识

自动化变量

  • $@ 表示目标(target)文件,就是冒号前面的那个文件
  • $^ 表示所有的依赖文件,就是冒号后面的那一堆文件
  • $< 表示第一个依赖文件,就是紧挨着冒号后面的一个文件
  • $* 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b,并且目标的模式是 a.%.b,那么,$* 的值就是 dir/a.foo
Read more »

一、总论

1.1 直观感受

这一章虽然名义上很简单,但是我个人觉得真的很难,其难度是要远超于 lab3 和 lab4 的。对于 lab3 和 lab4,其难度主要集中于复杂的函数调用关系上,如果理清了函数调用关系,会发现难度就很容易弄清了,而理清函数关系只要通过认认真真的看代码,整理就可以办到。而 lab5 的难度在于理解操作系统的设计,这种理解不是代码层次的“一个函数是调用了哪些函数,传了哪些参数?”这一类的问题,而是 MOS 是怎样设计的,为什么要这样设计的问题。这些问题在 lab4 中也出现了,比如异常处理的微内核设计。

1.2 文件系统微内核

这一章的微内核与 lab4 的异常处理的微内核是两个东西,这是最重要的一点。微内核设计都强调将原本属于操作系统的一部分功能分配出来(比如说异常处理,文件系统),分配给用户进程完成。在 lab4 中,微内核设计是把异常处理的功能分出来,让每个进程都具有异常处理的功能(以库函数的形式)。但是在 lab5 中,微内核设计确实将文件系统的实现分了出来,但是并不是配置到了每一个用户进程上,让每一个进程都有文件系统的能力,而是专门开设了一个用户进程专门提供文件系统的服务(这也是这个用户进程的大部分实现文件都被放置在了 fs 文件夹下,而不是 user 文件夹下的原因),而其他进程想要获得文件系统的服务,就要与这个特殊的用户进程进行通信,就好像之前与操作系统进行通信一样。

Read more »

一、总论

第三个单元作为从第二个单元的心态考验中解放的一个单元,很容易让人先入为主的松了一口气。这个单元干的事情确实要之前两个单元要少,这是因为其实我觉得 OO 最体现思维的“架构设计” 部分被 JML 剥夺了(这么说并不严谨,之后分析)。导致确实这个单元的任务重心并不在完成作业本身,这是大部分人呈现的情况。

但是人的精力总是一定的,没有被写 OO 本身浪费掉,就会被其他事情浪费掉。而这个单元恰恰有两件浪费精神的事情。一件是去优化算法的复杂度,,一件是去做测评。

关于算法时间复杂度的优化,我个人感觉是一种“被动卷”。这种卷带来的不好受并不是像前两个单元,因为简化一个表达式,或者改一种电梯策略的“主动卷”。而是一个必须参与的“默认卷”,一个很死板的东西。从三个方面分析:

  • 代价过大,比如说如果没有进行算法优化,不是没有性能分,而是直接被人 hack 掉,虽然分数确实不多,但是太令人恶心了。
  • 毫无美感,所谓的优化就是内部维护一个中间变量,不涉及算法(当然图算法也算用到了,但是最恶心的是其他的维护变量)
  • JML 无关,哪怕是图算法或者并查集,很精妙,但是跟 JML 没有关系,JML 是用于规范代码的行为的,算法是用来改善代码行为的。类似于一个是法律,一个是经济,要研究法律(JML),探讨的应该是“张三与 20 个老婆结婚但是都不领结婚证,算不算重婚?”这一类明显需要细细甄别的问题,而不应该探讨“如何让张三与 20 个老婆结婚但不领结婚证”这个问题(这是一个经济问题(算法))。
Read more »

总论

当我们去说元编程的时候,我觉得我们其实有很多方面,大致有如下几个:

  • 可以自动生成代码
  • 可以执行字符串源码
  • 在运行时可以修改源码

总之每一个都是对于“源码只读”这个规矩的挑战。对于 C 而言,可以用来生成代码,而对于 C++ 而言,可以用 template 来代替宏。而对于其他元编程能力,C++ 显得有些为难。

总之,需要明确,C++ template 有着和 C 的 define 相同的设计思路,可以看作是宏的强化。它提供了一种类型安全,方便调试的宏。这种思路在强调 template 和宏一样,本质上都是在产生源码。

Read more »

一、默认方法

1.1 总论

这是 CPP 与 Java 的OOP 最不同的地方。对于 Java ,基本上是没有默认的行为的,因为所有的对象其实都是指针,所以只有被定义过的方法会用到,而没有被定义过的方法,我们一点也不担心它会在什么其他地方出现,比如:

Student cnx;					// 我们不担心 cnx 内部是怎样的,因为这个就是一个指针
Student qs = cnx;				// 我们不担心 qs 与 cnx 的关系,因为只不过是对象指针的赋值,而跟实际的两个 Student 没有关系
System.out.println(qs);			// 我们不担心 qs 的字符串化,因为来自 Object 类
classroom.answerQuestion(qs)	// 我们不担心 qs 到底是怎么传入的,因为我们知道传入的就是一个指针
...
end of execution				// 我们不关心 qs 和 cnx 的释放,因为都是 JVM 负责的
```​

但是对于 CPP 来说,这些就都是问题了。


```cpp
Student cnx;					// 真的定义了一个 Student 对象,里面的内存情况是啥样的?
Student qs = cnx;				// 真的将一个对象复制给了另一个对象,那么是怎么复制的?
string s = qs;					// 发生了类型转换,这么多的类型,都是怎么转换的?
answerQuestion(qs)				// 我传进去的到底是个什么,我可以修改它吗?
...
end of execution				// 谁来释放 cnx 和 qs,没有人管一下吗?

可以看到正是因为 CPP 提供了强大的功能,导致在我们没有给我们的类进行任何设计的时候,给人的感觉就是完全不知道会发生什么?当然程序不会有任何二义性的东西存在,它一定会按照一定的规则去执行的。只不过这个规则被 CPP 隐藏起来了,导致我们入门的时候对于各种东西都十分迷茫。

Read more »

一、二义性和无义性

我们都知道程序是接受不了二义性的,当一条语句可以有两个执行结果的时候,那么程序就会接受不了。但是我们常常忽略“无义性”,这是我取得概念,意思是这条语句的执行结果是不确定的,这种不确定不是说结果不确定,比如 C 里面局部变量刚声明的时候值也是不确定的,但是没关系,而是说执行的规则是不确定的,那么就是不可以的。

但是由于 CPP 高度的自由性,所以他希望所有的规则都是使用者来制定,但是这种自定义规则制定前,也必然是存在一定的规则的,但是这个规则需要我们自己去了解。


二、声明、定义、初始化

Read more »