读《程序员的自我修养》有感
花了几天时间,研读了《程序员的自我修养——链接、装载与库》。个人感觉它在操作系统的底层机制和运行原理可谓是一本好书。可惜个人修为不够、根底太浅,对Linux同样不熟悉。等以后有积累了再来回读一番。
作者在序言里这样提出:计算机思想其实都是相通的,因为它们的核心思想是稳定不变的。这在我看来很大程度上是由于底层架构决定的,现行的计算机大多还是冯·诺依曼架构(量子计算机还有待成熟),因此万变不离其宗。
作者是个极爱钻研的人,处处可见一斑。对于最简单的入门程序“hello world”,作者对其提出了一连串的问题,这让我大感意外。“C语言程序为什么要被编译后才能运行?”、“编译过程中发生了什么?”、“不同情况下的编译结果是否相同”。如此简单的程序背后往往蕴含着复杂机制,如果单单是作为一个编译器的使用者,自然不必考虑这一切。但是如果希望进一步对此有了解,乃至能够创造出更好的机制,那么这都是不可或缺的。从“预处理”、“编译”到“汇编”再到“链接”,编译器开始为我们所理解。原来“#define”,“#include”在一开始就会被展开和替代。而在扫描过程中进行源代码的生成,之后再进行一步步的工作。于此我们可以见微知著,一部分原来有些强硬的规范其实有着深刻的内涵。例如宏语句使用时的注意事项(尽量加个括号),调试时“莫名其妙”被省略的变量(编译器提供的代码优化),开数组的时候全局变量往往能比局部变量分配更大的空间(堆栈与静态存储的不同)。这些小细节不是空穴来风、而是底层架构在上层的具象反映。而掌握底层架构,能够使我们更加精细地利用计算机的功能。
计算机科学有相当一部分需要依赖于自我的实践。作者为了了解操作系统及装载等这些关键的技术,他从头写了一个很小的内核、装载器及一个简单的运行库,做成了一个简单但较为完备的操作系统。尽管这些没什么直接收益(让GPA增加是不可能的,让自己发paper也是不可能的……),但是可以从中触类旁通。个人作业时写简单的hash的时候和大数组搜索算法时因为很久不动,导致码了很久,所以说仅仅是领悟思想是不够的,还要付诸实践。看这本书的时候看到了语法树部分,感觉它比数据结构课上所讲的中序表达式二叉树很为相似,就顺手写了个解释C语言代码(极简版)的代码,中间参考了别人的一些想法(主要是《C专家编程》),完成后让自己颇有所得(尽管这没法计入成绩(*^_^*))。
1 #include"stdio.h" 2 #include"string.h" 3 #include"ctype.h" 4 #include"stdlib.h" 5 6 #define MAXTOKENS 100 //设置语句中标识符的最大数量100 7 #define MAXTOKENLEN 64 //设置标识符的最大长度64 8 9 enum type_tag {IDENTIFIER,QUALIFIER,TYPE}; //IDENTIFIER=0,QUALIFIER=1,TYPE=2 10 11 struct token //结构体:标识符(token) 12 { 13 char type; //type:数据类型 14 char string[MAXTOKENLEN]; //string[MAXTOKENLEN]:存储标识符名称的数组 15 }; 16 17 int top=-1; 18 struct token stack[MAXTOKENS]; //stack[]:存储整句语句的标识符的数组(堆栈) 19 struct token these; //these:临时存储单个标识符的变量 20 21 #define pop stack[top--] 22 #define push(s) stack[++top] = s //push(s):将变量s压入stack数组顶端 23 24 enum type_tag classify_string(void) /*推断标识符的类型*/ 25 { 26 char *s = these.string; /*用指针s代替these的string数组使用(简洁)*/ 27 if(!strcmp(s,"const")) //如果string中是"const" 28 { 29 strcpy(s,"read-only"); //将s中的"const"翻译为"read-only" 30 return(QUALIFIER); 31 } 32 if(!strcmp(s,"volatile")) 33 return(QUALIFIER); 34 if(!strcmp(s,"void")) //下面一系列if判断,返回type:数据类型说明 35 return(TYPE); 36 if(!strcmp(s,"char")) 37 return(TYPE); 38 if(!strcmp(s,"signed")) 39 return(TYPE); 40 if(!strcmp(s,"unsigned")) 41 return(TYPE); 42 if(!strcmp(s,"short")) 43 return(TYPE); 44 if(!strcmp(s,"int")) 45 return(TYPE); 46 if(!strcmp(s,"long")) 47 return(TYPE); 48 if(!strcmp(s,"float")) 49 return(TYPE); 50 if(!strcmp(s,"double")) 51 return(TYPE); 52 if(!strcmp(s,"struct")) 53 return(TYPE); 54 if(!strcmp(s,"union")) 55 return(TYPE); 56 if(!strcmp(s,"enum")) 57 return(TYPE); 58 return(IDENTIFIER); 59 } 60 61 void gettoken(void) /*读取下一个标记到"these"*/ 62 { 63 char *p = these.string; 64 65 /*跳过空白字符*/ 66 while((*p = getchar()) == ' '); 67 68 if(isalnum(*p)) /*输入的字符以A-Z,0-9开头*/ 69 { 70 while(isalnum(*++p = getchar())); 71 ungetc(*p,stdin); 72 *p='\0'; 73 these.type = classify_string(); 74 return; 75 } 76 77 if(*p == '*') 78 { 79 strcpy(these.string,"pointer to"); 80 these.type = '*'; 81 return; 82 } 83 these.string[1] = '\0'; 84 these.type = *p; 85 return; 86 } 87 88 /*理解所有分析过程的代码段*/ 89 void read_to_first_identifier(void) 90 { 91 gettoken(); 92 while(these.type != IDENTIFIER) 93 { 94 push(these); 95 gettoken(); 96 } 97 printf("%s is ", these.string); 98 gettoken(); 99 return; 100 } 101 102 void deal_with_arrays(void) 103 { 104 while(these.type == '[') 105 { 106 printf("array "); 107 gettoken(); /*数字或']'*/ 108 if(isdigit(these.string[0])) 109 { 110 printf("0..%d ",atoi(these.string)-1); 111 gettoken(); /*读取']'*/ 112 } 113 gettoken(); /*读取']'之后的一个标记*/ 114 printf("of "); 115 } 116 return; 117 } 118 119 void deal_with_function_args(void) 120 { 121 while(these.type !=')') 122 { 123 gettoken(); 124 } 125 gettoken(); 126 printf("function returning "); 127 return; 128 } 129 130 void deal_with_pointers(void) 131 { 132 while(stack[top].type == '*') 133 { 134 printf("%s ", pop.string); 135 } 136 return; 137 } 138 139 void deal_with_declarator(void) /*处理标识符之后可能存在的数组或函数*/ 140 { 141 switch(these.type) 142 { 143 case '[' : deal_with_arrays();break; 144 case '(' : deal_with_function_args(); 145 } 146 147 deal_with_pointers(); 148 149 while(top >= 0) /*处理在读入到标识符之前压入堆栈中的符号*/ 150 { 151 if(stack[top].type == '(') 152 { 153 pop; 154 gettoken(); 155 deal_with_declarator(); 156 } 157 else 158 { 159 printf("%s ", pop.string); 160 } 161 } 162 return; 163 } 164 165 int main(void) 166 { 167 read_to_first_identifier(); /*将标记压入堆栈中,直到遇见标识符*/ 168 deal_with_declarator(); 169 printf("\n"); 170 return(0); 171 }
这本书感觉日后还要再重读一次,可能感悟会更深一些。不过在一周时间内在这本书上搜刮了一些东西,也算是不枉一读了,幸甚幸甚。