程序员的自我修养 day1

复制代码
链接、装载与库

计算机框架:应用层->运行库->操作系统层->硬件层
层与层之间需要通信,而通信需要协议,这个协议就叫接口 
下层为上层提供接口,上层作为接口的使用者

开发工具与应用层属于同一层次,他们都用操作系统应用编程接口
应用接口的提供者是运行库,不同的运行库提供不同的API,如windows运行库提供windows API 

操作系统与硬件接口叫做:硬件规格

操作系统的功能是提供抽象接口和管理硬件资源

所有应用程序都以进程的方式运行,每个进程都有自己独立的地址空间,使得进程间相互隔离,要通过通信访问

程序通过虚拟地址映射到实际的物理地址,这样多程序切换代码编写的函数跳转地址问题就能解决了 
进程隔离是通过虚拟地址实现的 

分段: 
分段对内存区域的映射还是按照程序为单位,如果内存不足,要进行换入换出到磁盘,这时候整个程序都要被换,造成大量磁盘访问,从而影响速度

分页:
一般按照4KB/页分,4GB内存可以分成1e6+页;
虚拟空间的页:虚拟页
物理内存的页:物理页
磁盘中的页:磁盘页
虚拟空间的页可以映射到同一物理页实现内存共享;
 
当程序需要用到磁盘页时,虚拟页找不到,硬件捕获页错误,然后从磁盘页中读出数据并装入内存;
这种机制能大量利用内存空间;

虚拟存储需要靠硬件支持,几乎所有的硬件都采用一个叫MMU的部件进行页映射(memory management unit) 
(CPU)->virtual address->(MMU)->physical address->(physical memory)括号内的是真实器件

线程:
线程被称为轻量级进程,是程序执行流的最小单元,由线程ID,当前指令指针寄存器集合和堆栈组成
线程共享进程的一些资源,也有自己独立的内存空间(寄存器、堆栈)

线程调度:
线程调度有三种状态:
运行: 线程正在执行 
就绪: 线程可以运行,但是CPU被占用了 
等待: 此时线程正在等待某一时间(I/O或同步),无法执行
一般把频繁等待的线程称为IO密集型线程
把很少等待的成为CPU密集型线程
IO密集型线程总是比CPU密集型线程容易得到优先级提升,因为CPU可以经常放养他们,从而不费力的完成这些任务;
饿死现象:高优先级的CPU密集型线程一直占用CPU导致低优先级的线程无法运行;

优先级改变三种情况:
1.用户指定优先级
2.根据进入等待状态的频繁程度提升或降低优先级
3.长时间得不到执行的而被提升优先级;

信号量、互斥量、临界区;
信号量,有多个满足线程的资源,可以由任意线程获取任意线程释放 
互斥量必须由获取的线程去释放。
信号量,互斥量能被多个线程获取该锁,而临界区只能被本进程获取 

条件变量针对的是批量线程
一个条件变量可以被多个线程等待,当条件发生时,可以唤起批量等待线程;



                第二章 
编译和链接
一段代码从写完到运行分为四个过程
预处理->编译->汇编->链接
静态语义:浮点数赋给整型变量,如果把浮点型赋给指针,运行前就能报错 
动态语义:除数为0,这里只有运行时才能确定报错; 

由汇编转成二进制文件形成的obj文件,最终需要链接才能形成/a.out文件
因为有些代码引用了其他源文件的去外部全局变量,需要后面链接才能确定它的绝对地址
现代编译器可以将一个源文件代码编译成一个未连接的目标文件,然后由连接器最终将这些目标文件链接起来形成可执行文件;

链接:
人们把每个源代码模块独立编译,然后组装起来的过程称为链接
链接的主要过程包括地址和空间的分配、符号决议、重定位

比如在main.c模块调用fun.c模块的foo函数,在编译器编译main.c的时候它并不知道foo的函数地址
于是它把调用foo的指令的目标地址搁置,等到最后链接的时候由链接器自动将目标地址修正,如果没有连接器,当foo函数改变地址后,需要人工跟着改变会造成很麻烦的修改过程;
比如目标A.c文件有个var变量,目标B文件想要访问var并修改var=42;
由于编译目标B文件时,编译器不知道var的目标地址,所以编译器在无法确定地址时,将Mov指令的目标地址置为0
等A,B,链接后再修改指令的目标地址,这个地址的修改也被叫做“重定向”


目标文件:源代码编译后未链接的文件(windows下.obj;linux下.o)
可执行文件:目标文件链接后的文件(windows下是.exe;linux下是.out)
动态链接库(windows下是.dll;linux下是.so)
静态链接库(windows下是.lib;linux下是.a) 

程序源代码编译后的指令经常放在代码段,这个段与存储方式分段的段不是一个概念
代码段常见的名字由.code或.text
全局变量和巨变静态变量数据经常放在数据段
数据段一般名字叫.data
未初始化的全局变量和局部静态局部变量放在.bss段,因为未初始化可能不用用上,因为不用为它们分配内存浪费空间


总的来说程序源代码被编译后主要分为两种段:程序指令和程序数据,代码段属于程序指令,而数据段和.bss段属于程序数据
把指令和数据分开的好处是程序被装载后,数据和指令分别映射到两个虚拟内存区域,数据区对于进程来说是可读写,而指令区域对于进程来说是只读的,可以方便设置权限
还有就是当系统运行多个该程序的副本时,指令是相同的,数据是不同的,因此只需要单独 的一份指令即可
 
 
关于extern "C"{
    void add(int,int);    
}
如果add是A.c编译的函数,此时我们在B.cpp编译的文件里要引用这个函数
由于c和cpp链接机制原因,如果不加extern "C",add的函数名可能会在B.cpp链接时找不到函数名 
在A.c中add编译时函数名字可能是_add,而在cpp规则下它编译时可能是_ZNaddEii,此时链接时就找不到add这个函数名从而报错
cpp的这种机制也造就了重载函数的可行性


#ifdef __cplusplus    //这是c++的内置宏 
extern "c" {
#endif

void add(int,int);

#if_def __cplusplus
}
#endif
这样就能在两种方式下正确链接了

编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号;
如int a = 2//全局变量 
可以通过gcc的__attribute__((a))  a = 1;//把强符号主动变成弱符号,目的是为了能让同名全局变量共存,了解知识,实用性不大

规则1.不允许强符号多次定义(即不同的目标文件或同一文件不能有同名强符号,即重定义) 
规则2.如果一个符号在某个目标文件是强符号,在其他文件都是弱符号,那么选择强符号;
规则3.如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的
如在A定义a为int,在B定义a为double则目标A与B链接后a占用8字节 
 
复制代码

 

posted @   matt-11  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示