链接

博客内容源自深入理解计算机系统

链接

概念

链接(link)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可执行于编译时(compile time),即在源代码被翻译成机器代码时;也可执行于加载时(load time),即在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(runtime),即应用程序来执行。在早期计算机系统中,链接是手动执行的。在现代系统中,链接是由叫链接器(linker)的程序自动执行的
链接器可以使得分离编译(separate compilation)成为可能,不用将一个大型的应用程序组织为一个巨大的源文件,而是可将它分解为更小,更好管理的模型,可以独立地修改和编译这些模块
为什么要了解链接及链接器?
理解链接器将有助于构造大型程序。构造大型程序时,经常会遇到缺少模块,缺少库或者不兼容的库版本引起的链接器错误。理解链接器如何解析引用,什么是库以及链接器是如何使用库开解析引用的,有助于解决这些错误。
理解链接器有助于避免一些危险的编程错误。Linux链接器解析符号引用时所作的决定可以影响程序的正确性。默认情况下,错误地定义多个全局变量的程序将通过链接器,而不产生任何警告信息。由此得到的程序会产生令人迷惑的运行时行为,而且非常难以调试。
理解链接将有助于理解语言的作用域规则是如何实现的。如,全局变量和局部变量之间的区别是什么?当定义一个具有static属性的变量或者函数时,实际到底意味着什么?
理解 链接有助于理解其他重要的概念。链接器产生的可执行目标文件在重要的系统功能中扮演着关键角色,如加载和运行程序,虚拟内存,分页,内存映射
理解链接有助于利用共享库。随着共享库和动态链接的重要性日益加强,链接成为一个复杂的过程。如许多软件产品在运行时使用共享库来升级压缩包装(shrink-wrapped)二进制程序。还有,大多数的Web服务器都依赖于共享库的动态链接来提供动态内容。
下面关于链接各个方面的讨论,从传统静态链接到加载时的共享库的动态链接,以及运行时的共享库的动态链接。所有的讨论是基于的环境为:一个运行Linux的x86-64系统,使用标准的ELF-64目标文件格式。不过无论系统、目标格式文件,基本的链接概念是通用的。

编译器驱动程序

看下面的C语言示例程序
///code/mian.c
int sum(int *a,int n);

int array[2]={1,2};

int main(){
  int val = sum(array,2);
  return val;
}
//code/sum.c
int sum(int *a,int n){
  int i,s = 0;
  for(){
    s += a[i];
  }
  return s;
}

大多数编译系统提供编译器驱动程序,它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。如GUN编译系统构造示例程序,用户使用shell调用GCC驱动程序:

linux> gcc -Og -o prog main.c sum.c

静态链接
图中概括了驱动程序在将示例程序从ASCII源码文件翻译成可执行目标文件。驱动程序首先运行C预处理器(cpp),将C源程序main.c翻译成一个ASCII码中间文件main.i:

cpp [other arguments] main.c /tmp/main.i

接着,驱动程序运行C编译器(cc1),将main.i 翻译成一个ASCII汇编语言文件main.s

cc1 /tmp/main.i -Og [other arguments] -o /tmp/main.s

然后,驱动程序运行汇编器(as),将main.s翻译成一个可重定位目标文件(relocatable object file)main.o:

as [other arguments] -o /tmp/main.o /tmp/main.s

驱动程序经过相同的过程生成sum.o。最后,它运行链接器程序ld,将main.o和sum.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件(executable object file)prog:

ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o

在Linux shell中运行可执行文件prog

linux> ./prog

shell 调用操作系统中一个叫做加载器(loader)的函数,将可执行文件prog中的代码和数据复制到内存,然后将控制转移到这个程序的开头。

静态链接

像Linux LD程序这样的静态链接器(static loader)以一组可重定位目标文件和命令行参数作为输入生成一个完全链接的、可加载和运行的可执行目标文件作为输出。
输入的可重定位目标文件由各种不同的代码和数据(section)组成,每一节都是一个连续的字节序列指令在一节中,初始化了的全局变量在另一节中,而未初始化的变量又在另外一节中。
为了构造可执行文件,链接器须完成两个主要任务
**

  • 符号解析**(symbol reolution)。目标文件定义和应用符号,每个符号对应于一个函数、一个全局变量或一个静态变量。其目的是将每个符号引用正好和一个符号定义关联起来
  • 重定位(relocation)
    。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,而
    重定位
    这些节,并修改对应的符号的引用,使得它们指向内存位置。链接器使用汇编器产生的重定位条目(relocation entry)的详细指令,不加甄别地执行这样的重定位

链接器中目标文件是字节块的集合,其块中,或包含程序代码,或包含程序数据,而其他包含引导链接器和加载器的数据结构。链接器把这些块连接起来,确定被连接块的运行时位置,并且修改代码和数据块中的各种位置

目标文件

目标文件有三种形式:
可重定位目标文件:包含二进制代码和数据,在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件
可执行目标文件:包含二进制代码和数据,可被直接复制发哦内存并执行
共享目标文件:一种特殊的可重定位目标文件,可在加载或者运行时被动态地加载进内存并链接
编译器和汇编器会生成可重定位目标文件(含共享目标文件)。链接器生成可执行目标文件。技术上讲,一个目标模块(object module)是一个字节序列,而一个目标文件(object file)是一个以文件形式存放在磁盘中的目标模块。有时会互换使用该些术语。
目标文件是按照特定的目标文件格式来组织的,各个系统的目标文件格式都不相同,从贝尔实验室诞生的第一个Unix系统使用的a.out格式。Windows使用可移植可执行(Protable Executable,PE)格式。Mac OS-X使用Mach-O格式。现代的x86-64 Linux和Unix系统使用可执行可链接格式(Executable and Linkable Format,ELF),无论使用各种格式,概念都是相似的。
可重定位目标文件
一个典型的ELF可重定位目标文件的格式如下,ELF头(ELF header)以一个16字节的序列开始,该序列描述了生成该文件的系统的字的大小和字节顺序
ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF大小,目标文件的类型(可重定位、可执行或共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量
不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
夹在ELF头和节头部表之间的都是节。一个典型的ELF可重定位目标文件包含下面几个节
.text已编译程序的机器代码
.rodata只读数据,如printf语句中的格式串和开关语句的跳转表;
.data已初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这些节不占实际的空间,仅仅是一个占位符。目标文件格式分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。
.bss(Block Storage Start):未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。实际上,不需要通过-g选项来编译一个程序来得到符号表信息。每一个可重定位目标文件在**.symtab中都有一个符号表**(除非使用STRIP去掉)。和编译器中符号表不同,.symtab符号表不包含局部变量的条目。
.rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般,任何调用外部函数或引用全局变量的指令都需要修改,调用本地函数的指令则不需要修改。注意,可执行目标中并不需要重定位信息。
.real.data被模块引用或定义的所有全局变量的重定位信息,一般,任何已初始化的全局变量,若它的初始值是一个全局变量或者外部定义的函数的地址,都需要被修改。
.debug一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始C源文件。只有以-g选项调用编译驱动程序时,才会得到这张表。
.line原始C源程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。
.strtab一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表是null结尾的字符串的序列。

符号和符号表
每个可重定位目标模板m都有一个符号表,它包含m定义和引用的符号和的信息。在链接器的上下文中,有三种不同的符号:

  • 由模块m定义并能被其他模块引用的全局符号。全局链接器符号对应非静态的C函数和全局变量。
  • 由其他模块定义并被模块m引用的全局符号。这些符号称为外部符号,对应于在其他模块中定义的非静态C函数和全局变量。
  • 只被模块m定义和引用的局部符号。他们对应于带static中的符号表不包含对应于本地非静态程序变量的任何符号。这些符号在运行时在栈中被管理。

定义为带有C static属性的本地过程变量是不在栈中管理的。相反,编译器在.data或.bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。如,假设在同一模块中的两个函数各自定义了一个静态局部变量x:

int f(){
	static int x = 0;
	return x;
}

int g(){
	static int x = 1;
	return x;
}

此时,编译器向汇编器输出两个不同名字的局部链接器符号。如,它可用x.1表示函数f中定义,而用x.2表示函数g中的定义。
利用static属性隐藏变量和函数名字:任何带有static属性声明的全局变量或者函数都是模块私有的(其他模块无法访问)。类似,任何不带static属性声明的全局变量和函数都是公共的,可被其他模块访问,局部变量?。

//main.c
#include "stdio.h"

int main(){
  extern void test();
  extern int a;
  extern int b;
  test();
  printf("a=%d.\n,b=%d.\n",a,b);

  return 0;
}
//test.c
#include "stdio.h"
//全局变量
int a,b;
//statci int a,b;//a,b只能模块内访问,模块外部无法访问;

void test(){
  a = 1;
  b = 2;

}

-bash-4.2$ sudo gcc -o main main.c test.c 
-bash-4.2$ ls
main  main.c  test.c
-bash-4.2$ ./main 
a=1.
,b=2.

局部变量

//main.c
#include "stdio.h"

int main(){
  extern void test();
  extern int a;
  extern int b;
  test();
  printf("a=%d.\n,b=%d.\n",a,b);

  return 0;
}
//test.c
#include "stdio.h"

void test(){
  //局部变量
  int a,b;
  a = 1;
  b = 2;

}
-bash-4.2$ sudo gcc -o main1 main.c test.c 
/tmp/ccUGrX8q.o: In function `main':
main.c:(.text+0x10): undefined reference to `b'
main.c:(.text+0x16): undefined reference to `a'
collect2: error: ld returned 1 exit status

若将需要调用变量写在头文件中,也可:

//test.h
int a,b;

//main.c

#include "stdio.h"
#include "test.h"

int main(){
  extern void test();
  test();
  printf("a=%d.\n,b=%d.\n",a,b);

  return 0;
}
// test.c
#include "stdio.h"
#include "test.h"
//int a,b;

void test(){
  //int a,b;
  a = 1;
  b = 2;
}
-bash-4.2$ sudo gcc -o main2 main.c test.c 
-bash-4.2$ ./main2
a=1.
,b=2.

其中,注意,GCC中调用头文件,#include <> :先去系统默认处,寻找头文件,若无,则在从所在文件夹中寻找;#include " ":先从本地文件夹中寻找,若无在从系统默认路径寻找。

符号表是由汇编器构造的,使用编译器输出到汇编语言.s文件中的符号。.symtab节中包含ELF符号表。这张符号表包含一个条目的数组,具体条目格式如下:

typedef struct{
  int name;
  char type:4,
  	  binding:4;
}ELF64_Symbol;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要求的操作系统是非常有意义的。 本文从管理员、用户的功能要求出发,4S店客户管理系统中的功能模块主要是实现管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需求相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流信息的查看及回复相应操作。
现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本微信小程序医院挂号预约系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此微信小程序医院挂号预约系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。微信小程序医院挂号预约系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,医生信息管理,医院信息管理,科室信息管理,预约信息管理,预约取消管理,留言板,系统管理。微信小程序用户可以注册登录,查看医院信息,查看医生信息,查看公告资讯,在科室信息里面进行预约,也可以取消预约。微信小程序医院挂号预约系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值