【读书笔记】程序员的自我修养总结(三)

【读书笔记】程序员的自我修养总结(三)


声明:引用请注明出处http://blog.csdn.net/lg1259156776/


说明:这是程序员的自我修养一书的读书总结,随着阅读的推进,逐步增加内容。


静态链接

1. 静态链接库

实际上就是一堆目标文件的归档,可以在cmd命令中使用命令ar t xx.lib可以解析出对应的obj文件,也可以通过ar -v -x xx.lib将目标文件解析出来。所以,需要了解的是目标文件到底是什么。

2. 目标文件

目标文件与可执行文件格式跟操作系统和编译器密切相关,不同的系统下会有不同格式,但大同小异。比如windows下为PE(Portable Executable),Linux下的ELF(Executable Linkable Format),都是基于COFF格式的变种。目标文件是源代码编译后但未进行链接的那些中间文件,跟可执行文件的内容与结构很相似,所以一般跟可执行文件格式一起采用一种格式存储。

目标文件中包含编译后的机器指令代码、数据,还包括链接时所需的信息,如符号表,调试信息、字符串等。一般以section节(或叫做segment 段)的形式存储。比如代码段,.text,全局变量和局部静态变量数据经常放入数据段,.data。可以借用DSP中的cmd文件中的形式:

/****************************************************************************/
/*  C6455.cmd                                                               */
/*  Copyright (c) 2010 Texas Instruments Incorporated                       */
/*  Author: Rafael de Souza                                                 */
/*                                                                           */
/*    Description: This file is a sample linker command file that can be    */
/*                 used for linking programs built with the C compiler and  */
/*                 running the resulting .out file on an C6455              */
/*                 device.  Use it as a guideline.  You will want to        */
/*                 change the memory layout to match your specific C6xxx    */
/*                 target system.  You may want to change the allocation    */
/*                 scheme according to the size of your program.            */
/*                                                                          */
/****************************************************************************/

MEMORY
{
    L2RAM:      o = 0x00800000  l = 0x00200000  /* 2MB L2 Internal SRAM */ 
    L1PRAM:     o = 0x00E00000  l = 0x00008000  /* 32kB L1 Program SRAM/CACHE */ 
    L1DRAM:     o = 0x00F00000  l = 0x00008000  /* 32kB L1 Data SRAM/CACHE */
    EMIFA_CE2:  o = 0xA0000000  l = 0x00800000  /* 8MB EMIFA CE2 */ 
    EMIFA_CE3:  o = 0xB0000000  l = 0x00800000  /* 8MB EMIFA CE2 */ 
    EMIFA_CE4:  o = 0xC0000000  l = 0x00800000  /* 8MB EMIFA CE2 */ 
    EMIFA_CE5:  o = 0xD0000000  l = 0x00800000  /* 8MB EMIFA CE2 */
    DDR2_CE0:   o = 0xE0000000  l = 0x20000000  /* 512MB EMIFB CE0 */ 
} 

SECTIONS
{
    .text          >  L2RAM
    .stack         >  L2RAM
    .bss           >  L2RAM
    .cio           >  L2RAM
    .const         >  L2RAM
    .data          >  L2RAM
    .switch        >  L2RAM
    .sysmem        >  L2RAM
    .far           >  L2RAM
    .args          >  L2RAM
    .ppinfo        >  L2RAM
    .ppdata        >  L2RAM

    /* COFF sections */
    .pinit         >  L2RAM
    .cinit         >  L2RAM

    /* EABI sections */
    .binit         >  L2RAM
    .init_array    >  L2RAM
    .neardata      >  L2RAM
    .fardata       >  L2RAM
    .rodata        >  L2RAM
    .c6xabi.exidx  >  L2RAM
    .c6xabi.extab  >  L2RAM
}

DSP采用的开发环境CCS,编译器也是gcc,输出的可执行文件为COFF,在CMD文件中,可以对代码段,数据段等所映射到的内存位置进行配置。有助于理解段页式存储。

先说一下它的好处:

<1> 由于数据对进程来说是可读写的,而指令只能读,将其分开存储有利于防止指令被改写;
<2> CPU有着强大的缓存(Cache)体系,将指令和数据分离有助于提高程序的局部性,从而提高Cache的命中率;
<3> 最重要的是动态链接库的概念之后,指令和数据分离,可以实现内存中只需要有一份指令的拷贝,而不用拷贝很多份儿,节省内存;而数据既可以是进程私有,也可以是共享的。

现在的浏览器,你可以打开很多副本,但是内存中的指令可能就只有一份拷贝,但是数据是多份儿的,大概就是这样子!

关于目标文件中的各个段,可能就不再详细描述。

3. 重定位表

前面说过,链接器在处理目标文件的时候,需要对目标文件中的某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用位置。这些重定位的信息都记录在ELF文件的重定位表中。

4. 链接的接口-符号

链接的目的是将不同的目标文件相互嵌和在一起,通过目标文件之间对地址的引用来实现,即函数和变量的地址的引用,在链接中,函数和符号统称为符号,函数名和变量名就是符号名。

每个目标文件都有一个相应的符号表,symbol Table,记录了目标文件里所有用到的符号,每个符号有一个对应的值,叫做符号值,对于变量和函数来说,符号值就是它的地址。

符号表中所有的符号分类如下:

  • 定义在本目标文件中的全局符号,可以被其他目标文件引用;
  • 在本目标中引用的全局符号,却没有在本目标文件中定义,一般被称为外部符号(External Symbol),即符号引用;
  • 段名,由编译器产生,它的值记录了该段的起始地址,比如.text,.data等;
  • 局部符号,这类符号只在编译单元内部可见。

实际上最关心的是全局符号,即上面的一二两个类别。因为能在链接过程起到作用的只有全局符号才能,其他的符号对于别的目标文件是不可见的。

由于全局符号在链接过程是全局可见的,所以如果编写的库文件中和当前的目标文件中有相同的符号名,那么就会发生冲突。为了防止类似的符号名冲突,UNIX下的C语言规定,C语言源代码文件中的所有全局变量和函数经过编译以后,相对应的符号名前面加上下划线,“_”。但如果是一个大型的软件,由不同的部门来开发,他们之间的命名规范如果不严格,则可能导致冲突,于是C++这样的语言使用了Namespace来解决不同模块下的符号冲突。

C++的符号修饰机制指的是C++语言支持不同参数类型的函数拥有一样的名字,即函数重载。实际上他们在编译的时候会进行一个函数签名,包含函数名,参数类型,所在类和命名空间等信息。

而名称修饰机制,也被用来防止静态变量,不同的编译器可能名称修饰方法不同,函数签名可能对应不同的修饰名称,由于不同的编译器采用不同的名字修饰方法,必然导致由不同编译器编译产生的目标文件无法正常相互链接。

5. “extern C”用法

C++中为了与C兼容,在符号管理上有一个用来声明或定义一个C的符号的“extern C”关键字用法:

extern “C”{
    int func(int);
    int var;
}

C++ 编译器会将extern “C”的大括号内部的代码当作C语言来处理。VC++平台会将C语言的符号进行修饰,即大括号中的func和var修饰后的符号为_func和_var,而C++部分的则按照C++的那一套进行修饰。

而下面的一段代码常常用来解决C/C++两种源码编译形式:

#ifdef __cplusplus
extern "C"{
#endif

void *memset(void *, int, size_t);

#ifdef __cplusplus
}
#endif

如果当前参与编译的是C++代码,memset会在extern “C”中被声明,按照C代码进行符号修饰;而如果是C代码,直接声明即可。(C语言不支持extern “C”,而__cplusplus这个宏是C++编译器默认定义的,如果是C++编译器参与的编译,则就默认定义了该宏。)。这段代码几乎在所有的系统头文件中都被利用。

6. 强弱符号与强弱引用

多个目标文件链接的时候含有相同名字全局符号的定义,链接的时候链接器会报错,这种符号的定义被称为强符号,而默认函数和初始化的全局变量为强符号,未初始化的全局变量为弱符号,强弱符号试着针对定义来说的,而不是针对符号的引用。

针对强弱符号的概念,链接器会按照如下规则处理与选择多次定义的全局符号:

  • 不允许强符号多次定义,否则报错;
  • 强符号冲掉弱符号;
  • 如果全是相同的弱符号,则选择占用空间最大的一个;

所以尽量不要使用多个不同类型的弱符号,否则很容易导致难以发现的程序错误。

对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,必须要被正确决议,如果没有找到该符号的定义,链接器就会报错。这种被称为强引用,与之对应的是弱引用。在处理弱引用时,如果该符号有定义,则链接器将该符号的引用决议,如果该符号未被定义,链接器对于该引用不报错;所以链接器处理强弱引用与处理强弱符号类似,只是对于未定义的弱引用,链接器不认为是一个错误。

弱符号和弱引用对于库十分有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖,从而使得程序可以使用自定义版本的库函数;或者程序可以对某些扩展功能模块的引用定义为弱引用,当将扩展模块与程序链接在一起的时候,功能模块可以正常使用,而去掉了某些功能模块,那么程序也可以正常连接,只是缺少了相应的功能,使得程序的功能更加容易裁剪和组合。

7. 调试信息

在目标文件中,还有调试信息在里面。

值得一提的是调试信息在目标文件中和可执行文件中占用很大的空间,往往比程序的代码和数据本身大好几倍,所以当开发完程序并要将它发布的时候,需要把这些对于用户没有用的调试信息去掉,以节省大量空间。

总结

经过这一章节的学习,忽然间懂了很多的东西,在我开发DSP的JPEG2000的库中帮助了我不少。


2015-10-23读书笔记 张朋艺

原文地址:https://www.cnblogs.com/huty/p/8518989.html