读《程序员的自我修养》有感


《程序员的自我修养》这本书其实最开始是信息安全这门课推荐的书,当时书买了,也看了点,后来就感觉跟没看是一样的,主要是之前根本就没怎么接触,也没写什么读书笔记或者做一些实验来加深对知识的理解,后来上了《程序设计与计算机系统》和《计算机病毒》这两门课,然后再次读了一下,收获颇丰!

这本书的全名叫做《程序员的自我修养---链接,装载与库》,但是不得不提的是编译这个过程也非常重要,书的第二章从一个简单的输出hello,world程序说起

 

 读《程序员的自我修养》有感_第1张图片

 


当在linux下面执行gcc hello.c或者在windows下面cl /c hello.c,这个过程实际上可以分为一下4个步骤:

 

预编译:

预编译过程主要处理源代码文件中以#开始的预编译指令,比如”#include”, ”#define”,处理规则如下:

  • 删除所有的#define,展开所有的宏定义
  • 删除所有注释,增加行号和文件名标识用于编译器调试
  • 保留所有的#pragma编译器指令,因为编译器要使用它们
  • 处理所有的条件预编译指令,比如#if,#ifdef

编译

编译过程主要是对预处理完的文件进行一系列的词法分析、语法分析、语义分析、代码生成、代码优化最终得到汇编代码。

词法分析:主要是扫描源程序将源代码的字符序列分割成一系列的TokenToken主要分为关键字,标识符,数字,字符串和特殊符号等,在扫描的过程之中也会将标识符加入符号表,数字,字符串常量等放入文字表以备后用。

语法分析:主要对词法分析得到的Token进行上下文无关语法(Context-free Grammar)分析得到语法树,于此同时还会进行语法检查,编译器报告语法分析阶段的错误。

语义分析:主要分为静态语义分析和动态语义分析,主要是确定表达式两边的类型,静态语义分析主要是在编译时期就确定(普通类型),而动态语义分析则是在运行时期进行确定(JAVA中的反射)。

代码生成:主要是将语法树转换成中间代码,比较常见的是三地址码和P代码,代码生成器主要是将中间代码转换成目标机器代码。

 

链接

当程序经过编译之后生成的机器代码中,有许多定义在其他模块的变量的地址是不知道的,所以这个时候链接器就出现了,链接器主要是将各个模块的代码段和数据段进行合并得到一个可执行的目标文件。链接主要要做两件事情,一件是符号的解析,另外一件事情是地址的重定位,符号的解析主要针对目标文件中的符号的定义和引用分为三种不同的符号:

  •  有自己定义但是其他模块可以进行引用的全局符号
  •  引用在其他模块中的定义的符号称为外部符号
  •  只有自己才能引用自己定义的符号称为本地符号

重定位主要干以下两件事情:

  • 链接器将所有相同类型的section合并成同一类型的section
  • 链接器根据重定位条目来修改代码节和数据节中的符号引用使其指向正确的运行时地址。(重定位条目主要是汇编器遇到对最终位置未知的目标引用,它会告诉链接器在合并目标文件时如何得到最终的地址)

链接主要分为静态链接和动态链接这两种方式:

  1. 静态链接:主要是在链接过程中,吧目标文件需要的库或者其他模块的代码和代码合并生成最终的可执行代码
  2.  动态链接:主要将引用的其他模块的代码和数据合并放在运行期执行,比较重要的概念是GOTPLTGOT主要存放全局变量和外部函数的地址,PLT主要是在函数,变量第一次引用时进行绑定(符号查找,地址的重定位)。

 

 

库与运行库

我认为如果想研究程序的运行环境,首先要知道程序与内存之间的关系,linux下面给进程分配了4G的空间,下图是网上截取的一个详解图:

 

 读《程序员的自我修养》有感_第2张图片

然后书中讲了栈和调用惯例(calling convention,调用惯例是函数的调用方和被调用方的一个约定,当双方遵循约定时,函数才能正确的被调用,主要是一下几个方面:

  • 函数参数的传递顺序和方式(参数压栈的顺序)
  • 栈的维护方式(windows下面是被调用者回收参数,linux下面是调用者回收参数,这就是你会看到函数后面会看到诸如add 0x8,%esp的原因)
  • 名字修饰符的策略,不同的调用惯例用不同的修饰符修饰策略,例如cdecl是下划线+函数名而windows下面的stdcall则是下划线+函数名+@+参数的字节数

在书的末尾还了解到了C运行库的实现,书中运行库主要包括以下几个方面:

  •  基本的进程相关操作(exit
  • 支持堆操作(mallocfree
  •  直接文件操作(fopen,fread,fwrite,fclose
  •  支持基本的字符串操作(strcpy,strlen,strcmp)
  • 支持atexit()函数
  •  跨平台(支持windowslinux)

 

其他

书中还夹杂着对windowslinux中的文件格式的对比,windows主要是PE格式,书中提到了Entry point(程序的入口地址),image base(程序装载进进程的基地址)而PE HEADER则是程序载入内存空间头部,头部后面就是大家熟悉的.text.data段等,以及动态链接的不同之处,windows主要是DLL(这就是为什么system32下面全是dll的原因,这也是为什么你打开wordppt的时候前面会有一段的延迟时间,主要是在链接DLL),而linux下面主要是使用.so

感悟

CSAPP的辅助之下,看完这本书之后,自己对程序的编译,链接,装载,运行各个过程有了更深刻的认识,代码指令是如何保存的,库文件如何与应用程序代码静态链接,应用程序如何被装载到内存中并开始运行,动态链接如何实现,C/C++运行库的工作原理,以及操作系统提供的系统服务是如何被调用的,偶尔会从底层思考上层的设计原理和实现机制(java中异常的处理,代码的优化等)。

所以说读完这本书真是受益匪浅啊,自己在学习知识方面也慢慢趋于对知识深层次的理解,深入挖掘问题的本质是什么,而不是像以前那样以为自己懂了,认为自己可以不用看了,但实际上自己可能没真正理解它的真正意思,另外学习知识的过程有时候是一个反复的过程,是一个慢慢咀嚼升华的过程,每一次阅读思考之后,新的理解新的感悟新的收获也会随之而来,我想这也就是学习的乐趣吧!

说句真心话,这P大点文字真心不算读后感,没办法,个人能力问题(贴在这里感觉丢人了),但是待我在看几遍在重新修改这篇文章!

写博客是为了更好的思考,所以让自己成为一个持续学习和思考的人,并只写自己真正思考和总结之后的产物!(这是最近看刘未鹏博客的收获这段话写在这里来鞭策一下自己吧!

 

你可能感兴趣的:(读后感)