程序员的自我修养--读书笔记
1:注意不要反回指向栈内存的指针或引用,因为在函数返回时改内存已经被销毁了
2:C/C++没有办法知道指针所指的内存容量大小 当数组作为参数传递时,数组将退化成相同类型的指针 不要指望要指针参数去申请动态内存,因为函数会为产生一个临时变量指向参数的内存,当函数内分配内存时,将内存的地址赋给了临时参数,而没有给实参赋值,所有实参没有发生任何变化,应该修改的是指针所指的内容,而不是修改指针的指向,所有可以用指向指针的指针
3:重载和内联机制既可用于全局函数也可用于类的成员函数,const和virtual机制即用于类的成员函数
4:在继承关系中,非虚方法:调用指针类型的方法;虚方法:调用指针所指的对象类型的方法 非虚方法和默认参数都是静态绑定,在继承关系中只跟指针类型有关,跟指针所指的对象的实际类型无关
5:互相引用的两个类,两个类最好声明在同一个头文件中,定义可以放在同一个或两个的文件中;这样即解决了互相引用的问题,同时解决了在一个类中不能正确delete另一个类
6:处理#include预编译指令,将被包含的文件插入到预编译指令的位置,这个过程是递归进行的
7:C语言的编译后执行语句都编译成机器代码,保存在.text段
.data:已初始化的全局变量和局部静态变量
.bss:为未初始化的全局变量和局部静态变量预留位置而已(不占磁盘空间,运行时当然是占空间的)
.rodata:存放只读数据,const修饰的变量,常量字符串;(传说中的字符串池,晕,别说得这么高级,只不过是在编译的时候就分配好了内存)
8:程序源代码被编译后主要分成两种段:程序指令和程序数据 分开的好处:
1:数据和指令分别被映射到两个虚存区域,数据可读写,指令只读;
2:CPU的高级缓存分为数据缓存和指令缓存
3:当程序有多个副本时,可以共享指令,有各自的数据,资源(图片)共享
9:可执行文件中的代码段和数据段都是由输入的目标文件中相应的段合并而来的
10:作用域:全局变量不管定义在哪里(.h或.cpp)整个解决方案都可见,定义在头文件中的静态全局变量整个解决方案都可以见,定义在实现文件(.cpp)中的静态变量只有这个文件可见,类中的public静态变量作用域是整个解决方案,可以通过类名使用这个静态变量,而private静态变量作用域则是这个类,方法中的静态变量作用域就是这个方法
11:.text和.data在文件和虚拟地址都要分配空间 .bss在文件中不分配空间,而要分配虚拟地址空间,因为在文件中它根本没有内容
12:链接过程:1空间与地址分配;2符号解析与重定位 VMA:virtual Memory Address虚拟地址 链接前虚拟地址都是空,链接后虚拟地址都分配好了
13:目标文件代码段的起始地址以0x00000000开始,等到空间分配完成以后,各个函数才会情定自己在虚拟地址空间中的位置
14:在编译的时候每个目标文件都会有一个符号表,如果A文件引用了B文件中的变量或方法,那么在符号表中就会标记这些变量或方法是没有定义了,在链接的时候如果没有找到这些变量或方法的定义,在链接的时候就会报符号未定义错误
15:静态装入:程序执行是所需要的指令和数据必须在内存中才能正常运行,最简单的办法就是将程序运行所需要的指令和数据全部装入内存中
动态装入的基本原理:程序运行有局部性,将程序最常用的部分驻留在内存中,不太常用的数据存放在磁盘中
16:创建一个独立的虚拟地址空间:将虚拟空间和物理空间映射 读取可执行文件头,并建立虚拟空间与可执行文件的映射关系:虚拟空间和执行文件映射 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行
17:Windows平台下用C++编写动态链接库要尽量遵循以下几个指导意见:
1:所有的接口函数都应该是抽象的,所有的方法都应该是纯虚的
2:所有的全局函数都应该使用extern C来防止名字修饰的不兼容,并且导出函数都应是_stdcall调用规范的, 这样即使用户本身的程序是默认以_cdecl方式编译的,对于 DLL的调用也能够正确
3:不要使用C++标准库STL
4:不要使用异常
5:不要使用虚析构函数,可以创建一个destory方法并且充值delete操作符并调用destory()方法
6:不要再DLL里面申请内存,而且在DLL外释放(或者相反),不同的DLL和可执行文件可能使用不同的堆,在一个堆里面申请的内存而在另一个堆里面释放会导致错误,对于内存分配相关的函数不应该是inline的,以防止它在编译时被展开到不同的DLL和可执行文件中
7:不要再接口中使用重载方法,因为不同的编译器对于vtable的安排可能不同
18:栈一般保存的内容:
1:函数的返回地址和参数
2:临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
3:保存上下文:包括函数调用前后需要保持不变的寄存器
19:多态的实现原理:
1:含有虚方法的类都有一个虚函数表
2:子类的虚方法会覆盖父类对应的虚方法
3:含有虚方法的类的每个实例都有一个指向虚方法表的指针,如果虚继承的话可能会有多个
4:根据3中的指针调用虚方法表中对应的虚方法
20:全局构造与析构:
编译器将两个段.init和.finit这两个段拼成两个函数_init()和_finit(),这两个函数先后于main函数执行,当然main函数并不是程序的入口,_start才是入口函数,.init段里面有个数组,数组中存放所有全局构造函数的指针,在执行函数_init()时会执行全局变量的构造函数,也就是说在调用main函数前,全局变量已经初始化好了,main函数执行完成之后,在执行_finit(),即全局变量的析构。
对每个编译单元(.cpp),编译器会遍历其中所有的全局变量,生成一个特殊的函数_GLOBAL_I_Hw,这个函数的作用就是初始化当前编译单元中的所有全局变量,如果这个特殊函数存在(即有全局变量),那么编译器会在目标文件的.ctors段中存放这个函数的一个指针,连接器在链接所有的目标文件的时候,会将同名的段合并在一起,每个目标文件的.ctors段也就合并在一起了,这样.ctors段中存放的就是每个目标文件中全局构造函数的指针,执行这些全局构造函数,全局变量就初始化好了。
全局析构的过程我想聪明的你不看也应该知道个大概,
21:未完.....待续......