程序员的自我修养八

正常情况下,编译出来的可执行库或可执行文件里面带有符号信息和调试信息,这些信息子啊调试时非常有用。

 

对于构造函数来说,属性中优先级数字越小的函数将会在优先级大的函数之前运行;而对于析构函数来讲,则刚好相反。先申请的资源后释放,符合资源的释放的一般原则。

 

一般的共享库都是动态链接的ELF共享对象(.so),事实上,共享库还可以是符号一定格式的链接脚本文件,通过它,可以把现有的共享库通过一定方式组合起来,对用户而言,就是一个新的共享库

 

Dll文件的扩展名不一定是.dll,也可能是别的,比如ocx(ocX控件)或是.CPL(控制面板程序)

 

Dll文件更强调模块化,windows平台上大量软件通过升级dll的形式进行自我完善,微软经常将这些升级补丁累计到一定程度形成一个软件更新包(servicepacks)

 

Active X技术就是基于这种运行时加载机制实现的

 

一个DLL在不同的进程中拥有不同的私有数据副本

 

PE中两个常用概念(baseaddress)和相对地址(RVA,relativevirtual address),当一个PE文件被装载时,其进程空间中的起始地址就是基地址,对于任何一个PE文件来说,它都有一个优先装载的基地址,这个值就是PE文件头的Image Base。对于一个可执行EXE文件来说,image base一般值是0x400000,dll的是0x100000,windows在装载dll时,会先把他装载至image base指定的虚拟地址;若该地址区域已被其他模块占用,则PE装载器会选用其他空闲地址。而相对地址就是一个地址相对于基地址的偏移。

 

一个DLL中由两个数据段,一个进程间共享,另外一个私有

 

程序使用DLL过程 其实就是引用dll中的导出函数和符号的过程,即导入过程

 

Msuc的链接符提供了指定输出文件的基地址的功能

 

Windows系统本身自带了很多系统的dll,如kernel32.dll,ntdll.dll,shell32.dll,user3

2.dll,nsvcrt.,dll。这些基本上是windows的应用程序运行时都要用到的。Windows系统就在进程空间中专门划出一块0x70000000~0x80000000区域,用于映射这些常用的系统dll。Windows在安装时就把这些地址分配给这些dll,调整这些dll的基地址使得它们之间相互不冲突,从而在装载时就不要进行重定基址了。

 

一个dll中每一个导出的函数对应的唯一序号

 

在windows平台下,要尽量遵循以下指导意见:

所有的接口都应该是抽象的。所有的方法都应该是纯虚的(或者也可以是inline方法)

所有的全局函数都应该使用extern”c”来防止名字修饰的不兼容,并且导出函数都应该是_stdcall调用规范(COM的DLL都使用这样的规范),这样即使用户本身的程序是默认以_cdecl方式编译的,对于dll的调用也能够正确

 

不要使用C++标准卡STL

不要使用异常

不要使用虚构函数。可以创建一个destroy()方法并且重载delete操作符并且调用destroy()

不要在dll里面申请内存,而且在dll外释放(或相反)。不同的dll文件和可执行文件可能使用不同的堆。A堆申请B堆释放会导致错误。对于内存分配相关的函数不应该是Inline的,以防止它在编译时不同的DLL和可执行文件。

 

不要在接口中使用重载方法(overloadedmethods),一个方法使用多重参数。因为不同的编译器对于不同的vtable的安排可能不同

 

在,NET框架下,一个程序集(assembly)有两种类型:应用程序(即exe可执行文件)集以及库程序集(dll动态链接库)集。一个程序集包括一个或多个文件,所以需要一个清单文件来描述程序集。这个清单文件叫做manifest文件,manifest文件描述了程序集的名字,版本号以及程序集的各种资源,同时描述了该程序集的运行所依赖的资源,包括dll以及其他资源文件等。

 

栈:栈用于维护函数调用的上下文,离开了栈函数调用就没法实现。栈通常在用户空间的最高地址分配,通常由数兆字节的大小

 

堆:堆是用来容纳应用程序动态分配的内存区域,当程序使用malloc或new分配内存方向时,得到的内存来自堆里。堆通常存在于栈的下方(低地址方向)。在某些情况下,堆也可能没有固定同一的存储区域。堆一般比栈大很多,可以有几十至数百兆字节的容量。

 

可执行文件映像:存储着可执行文件组爱内存里的映像,由装载器在装载时将可执行文件的内存读取或映射到这里

 

保留区:不是单一的内存区域,而是对内存中收到保护而禁止访问的内存区域的总称。大多数操作系统里,极小的地址通常都是不允许访问的,如NULL

 

在经典的操作系统里,栈总是向下增长的。在i386中,栈顶由称为esp的寄存器进行定位。压栈的操作使栈顶的地址减小,弹出的操作使栈顶的地址增大

 

栈保存了一个函数调用所需要的维护信息,被称为堆栈帧(stackframe)或行动记录(activate record)。其中一般包括:函数的返回地址和参数;临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;保存的上下文:包括在函数调用前后需要保持不变的寄存器

 

在i386中,一个函数的活动记录用ebp和esp这两个寄存器判定范围。Esp始终指向栈的顶部,同时也就指向了当前函数的活动记录的顶部。而ebp寄存器指向了函数活动记录的一个固定位置,ebp寄存器又被称为帧指针(frame pointer)

 

在C++里返回一个对象的时候,对象要经过2次拷贝构造函数的调用才能够完成返回对象的传递。1次拷贝到栈上的临时对象里,另一次把临时对象拷贝到存储返回值的对象里

 

管理着堆空间分配的往往是程序的运行库

 

进程的地址空间中,除了可执行文件、共享库和栈之外,剩余的未分配的空间都可以被用作堆空间

 

Linux提供了两种堆空间分配的方式,即两个系统调用:一个是brk()系统调用,另外一个是mmap()。Brk()的作用实际上就是设置进程数据段的结束地址,即它可以扩大或缩小数据段(linux下数据段和BSS合并在一起称为数据段)。如果我们将数据段的结束地址向高地址移动,那么扩大的那部分空间就可以被我们使用,把这块空间拿来作为堆空间是最常见的做法之一。

 

Mmap()的作用和windows下的virtual alloc 很相似,它的作用就是向系统申请一段虚拟地址空间,当然这块虚拟地址空间可以映射到某个文件(这也是系统调用最初的作用),当它不把地址空间映射到某个文件时,我们又称这块空间为匿名空间(anonymous),匿名空间可以拿来作为堆空间

 

Glibc的malloc函数这样处理用户的空间请求:对于小于128KB的请求,它会在现有的堆空间里面按照堆分配算法为它分配一块空间并返回;对于大于128KB的请求,他会使用mmap()函数为它分配一块匿名空间,然后在这个匿名空间中为用户分配空间”

 

在有共享库的情况下,留给堆可以用的空间还有两处。第一处是从BSS段结束到0x40000000,第二处是以共享库到栈的这块空间。这两块空间大小都取决于栈、共享库的大小和数量。

 

在使用virtual malloc的函数申请空间时,系统要求空间大小为页的整数倍,即对于X86系统来说,必须是4096字节的整数倍

 

堆管理器存在于windows的两个位置,一个是位于ntdll.dll中,这个dll是windows操作系统用户层的最底层dll,它负责windows子系统dll与windows内核之间的接口,所有用户程序、运行时库和子系统的堆分配都是使用这部分的代码。而在windows内核ntoskrnl.exe中,还存在类似的堆管理器,它负责windows内核中的堆空间分配,windows内核,内核组件、驱动程序使用堆时用到的都是这份堆分配代码,内核堆管理器的接口都由RealHeap开头。

展开阅读全文

没有更多推荐了,返回首页