就算理解了构建的过程,NEMU 代码依然很难读?
NEMU 对大部分同学来说是一个 “前所未有大” 的 project。
先大致了解一下
tree
要翻好几个屏幕find . -name "*.c" -o -name "*.h"
(110+ 个文件)find ... | xargs cat | wc -l
C 语言代码,都是从 main()
开始运行的。那么哪里才有 main
呢?
main.c
,估计在里面The UNIX Way (无须启动任何程序,直接查看)
grep -n main $(find . -name "*.c") # RTFM: -n
find . | xargs grep --color -nse '\<main\>'
Vim 当然也支持
:vimgrep /\<main\>/ **/*.c
:cn
, :cp
, ...比想象中短很多……
int main(int argc, char *argv[]) {
init_monitor(argc, argv);
engine_start();
return is_exit_status_bad();
}
Comments
init_monitor
代码在哪里?这个函数的名字起的很好,看了就知道要做什么
-b
, -l
, ...getopt
→ RTFM!失败的尝试:man getopt
→ getopt (1)
成功的尝试
man -k getopt
→ man 3 getopt
意外之喜:man 还送了个例子!跟 parse_args
的用法一样耶
The friendly source code
--help
帮助信息如何让我们的 NEMU 打印它?
make run
到底做了什么-n
选项开始痛苦的代码阅读之旅:
坚持 !
static inline void parse_args(int argc, char *argv[]) { ... }
parse_args
函数是 static, inline 的,这是什么意思?
联合使用
我们都知道,如果在两个文件里定义了重名的函数,能够分别编译,但链接会出错:
/* a.c */ int f() { return 0; }
/* b.c */ int f() { return 1; }
b.c:(.text+0x0): multiple definition of f; a.c:(.text+0xb): first defined here
这也是为什么不在头文件里定义函数的原因
vector
的实现就是直接粘贴进去的如果你的程序较短且性能攸关,则可以使用 static inline 函数定义在头文件中。例子 (**/x86/**/reg.h
):
static inline int check_reg_index(int index) {
assert(index >= 0 && index < 8);
return index;
}
check_reg_index
完全可以单独放在一个 C 文件里,头文件中只保留声明:
int check_reg_index(int index);
check_reg_index(0)
编译优化成零开销#define assert(cond) if (!(cond)) panic(...);
注意特殊情况:
if (...) assert(0); // 上面的assert对么?
else ...
#define assert(cond) \
do { \
if (!(cond)) { \
fprintf(stderr, "Fail @ %s:%d", __FILE__, __LINE__); \
exit(1); \
} \
} while (0)
#define assert(cond) ({ ... })
之后的历程似乎就比较轻松了。有些东西不太明白(比如 init_device()
),但好像也不是很要紧,到了 welcome()
:
static inline void welcome() {
...
printf("Welcome to \33[1;41m\33[1;33m%s\33[0m-NEMU!\n",
str(__ISA__)); // bad code! jyy doesn't like it.
}
哇,还能顺带打印出编译的时间日期,奇怪的知识又增加了!
“我决定挑战自己,坚持在命令行中工作、使用 Vim 编辑代码。但在巨多的文件之间切换真是一言难尽。”
上手以后还在用 grep 找代码?
Marks (文件内标记)
ma
, 'a
, mA
, 'A
, ...Tags (在位置之间跳转)
:jumps
, C-]
, C-i
, C-o
, :tjump
, ...Tabs/Windows (管理多文件)
:tabnew
, gt
, gT
, ...Folding (浏览大代码)
zc
, zo
, zR
, ...更多的功能/插件
刚拿到手,VSCode 的体验并不是非常好
c_cpp_properties.json
tasks.json
launch.json
听说你的程序又 Segmentation Fault 了?
exec.c
也太难读了吧 (元编程,害死人)
static inline def_EHelper(gp1) { // ???
...
EMPTY(0)
// EMPTY(idx) => EX(idx, inv)
// EX(idx, inv) => EXW(idx, inv, 0)
// !@%#&%^!#@&%!^@%#$%*^!#@*
}
产生 “这是什么操作” 的困惑:
gcc -E
不是编译错误吗……)我们既然知道 Makefile 里哪一行是 .o → .c 的转换
gcc -E
是不是就行了?$(OBJ_DIR)/%.o: src/%.c
@$(CC) $(CFLAGS) $(SO_CFLAGS) -c -o $@ $<
@$(CC) $(CFLAGS) $(SO_CFLAGS) -E -MF /dev/null $< | \
grep -ve '^#' | \
clang-format - > $(basename $@).i
敲黑板:征服你畏惧的东西,就会有意想不到的收获。
读代码 ≠ “读” 代码
信息来源
/etc/hosts
中屏蔽百度