1. 多目标文件的链接

摘自:linux c编程一站式学习

http://learn.akae.cn/media/index.html

可以学会在linux下将多个c语言文件一起编译。

现在我们把例 12.1 “用堆栈实现倒序打印”拆成两个程序文件,stack.c实现堆栈,而main.c使用堆栈:

/* stack.c */
char stack[512];
int top = -1; void push(char c)
{
stack[++top] = c;
} char pop(void)
{
return stack[top--];
} int is_empty(void)
{
return top == -1;
}

这段程序和原来有点不同,在例 12.1 “用堆栈实现倒序打印”top总是指向栈顶元素的下一个元素,而在这段程序中top总是指向栈顶元素,所以要初始化成-1才表示空堆栈,这两种堆栈使用习惯都很常见。

/* main.c */
#include <stdio.h> int a, b = 1; int main(void)
{
push('a');
push('b');
push('c'); while(!is_empty())
putchar(pop());
putchar('\n'); return 0;
}

ab这两个变量没有用,只是为了顺便说明链接过程才加上的。编译的步骤和以前一样,可以一步编译:

$ gcc main.c stack.c -o main

也分可以多步编译:

$ gcc -c main.c
$ gcc -c stack.c
$ gcc main.o stack.o -o main

如果按照第 2 节 “main函数和启动例程”的做法,用nm命令查看目标文件的符号表,会发现main.o中有未定义的符号pushpopis_emptyputchar,前三个符号在stack.o中实现了,链接生成可执行文件main时可以做符号解析,而putcharlibc的库函数,在可执行文件main中仍然是未定义的,要在程序运行时做动态链接。

我们通过readelf -a main命令可以看到,main.bss段合并了main.ostack.o.bss段,其中包含了变量astackmain.data段也合并了main.ostack.o.data段,其中包含了变量btopmain.text段合并了main.ostack.o.text段,包含了各函数的定义。如下图所示。

图 20.1. 多目标文件的链接

为什么在可执行文件main的每个段中来自main.o的变量或函数都在前面,而来自stack.o的变量或函数都在后面呢?我们可以试试把gcc命令中的两个目标文件反过来写:

$ gcc stack.o main.o -o main

结果正如我们所预料的,可执行文件main的每个段中来自main.o的变量或函数都排到后面了。实际上链接的过程是由一个链接脚本(Linker Script)控制的,链接脚本决定了给每个段分配什么地址,如何对齐,哪个段在前,哪个段在后,哪些段合并到同一个Segment,另外链接脚本还要插入一些符号到最终生成的文件中,例如__bss_start_edata_end等。如果用ld做链接时没有用-T选项指定链接脚本,则使用ld的默认链接脚本,默认链接脚本可以用ld --verbose命令查看(由于比较长,只列出一些片断):

$ ld --verbose
...
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT("elf32-i386", "elf32-i386",
"elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
...
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rel.dyn :
...
.rel.plt : { *(.rel.plt) }
...
.init :
...
.plt : { *(.plt) }
.text :
...
.fini :
...
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
...
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) }
...
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
...
.ctors :
...
.dtors :
...
.jcr : { KEEP (*(.jcr)) }
...
.dynamic : { *(.dynamic) }
.got : { *(.got) }
...
.got.plt : { *(.got.plt) }
.data :
...
_edata = .; PROVIDE (edata = .);
__bss_start = .;
.bss :
...
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
...
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
...
} ==================================================

ENTRY(_start)说明_start是整个程序的入口点,因此_start是入口点并不是规定死的,是可以改用其它函数做入口点的。

PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;是Text Segment的起始地址,这个Segment包含后面列出的那些段,.plt.text.rodata等等。每个段的描述格式都是“段名 : { 组成 }”,例如.plt : { *(.plt) },左边表示最终生成的文件的.plt段,右边表示所有目标文件的.plt段,意思是最终生成的文件的.plt段由各目标文件的.plt段组成。

. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));是Data Segment的起始地址,要做一系列的对齐操作,这个Segment包含后面列出的那些段,.got.data.bss等等。

Data Segment的后面还有其它一些Segment,主要是调试信息。关于链接脚本就介绍这么多,本书不做深入讨论。

linux c 链接详解1-多目标文件链接的更多相关文章

  1. linux常用命令详解 (二)文件处理命令

    ◆ 文件处理命令:file.mkdir.grep.dd.find.mv.ls.diff.cat.ln: 系统信息存放在文件里,文件与普通的公务文件类似.每个文件都有自己的名字.内容.存放地址及其它一些 ...

  2. Linux 链接详解----静态链接实例分析

    由Linux链接详解(1)中我们简单的分析了静态库的引用解析和重定位的内容, 下面我们结合实例来看一下静态链接重定位过程. /* * a.c */ ; void add(int c); int mai ...

  3. Linux curl 命令详解

    命令概要 该命令设计用于在没有用户交互的情况下工作. curl 是一个工具,用于传输来自服务器或者到服务器的数据.「向服务器传输数据或者获取来自服务器的数据」 可支持的协议有(DICT.FILE.FT ...

  4. Linux常用命令详解—基于CentOS7

    ## Linux 目录- /:根目录,一般只存放目录,不存放文件- /bin -> /usr/bin:可执行二进制文件的目录,也是常用命令目录,如常用的命令 ls.cat.mv 等- /boot ...

  5. Linux常用命令详解下

    Linux常用命令详解 目录 一.Linux常用命令 1.1.查看及切换目录(pwd.cd.ls.du) 1.2.创建目录和文件(mkdir.touch.ln) 1.3.复制.删除.移动目录和文件(c ...

  6. 【初级】linux rm 命令详解及使用方法实战

    rm:删除命令 前言: windows中的删除命令大家都不陌生,linux中的删除命令和windows中有一个共同特点,那就是危险,前两篇linux mkdir 命令详解及使用方法实战[初级]中我们就 ...

  7. Linux netstat命令详解

    Linux netstat命令详解 一  简介 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多 ...

  8. linux curl用法详解

    linux ‍‍curl用法详解 ‍‍curl的应用方式,一是可以直接通过命令行工具,另一种是利用libcurl库做上层的开发.本篇主要总结一下命令行工具的http相关的应用, 尤其是http下载方面 ...

  9. Linux find命令详解

    转自Linux find命令详解 一.find 命令格式 1.find命令的一般形式为: find pathname -options [-print -exec -ok ...] 2.find命令的 ...

  10. linux lsof命令详解

    linux lsof命令详解 简介 lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访 ...

随机推荐

  1. redis还要做

    RedisTemplate对各种数据类型的操作记录. Redis深度历险:核心原理和应用实践 https://www.cnblogs.com/kismetv/p/8654978.html

  2. Echarts和Highcharts学习笔记01——入门了解

      Echarts是国内百度团队开发的(开源),基于Canvas,适合数据量较大的情况: Highcharts是国外的(商用需授权),基于SVG,方便自己定制,但能使用的图表类型有限: Echarts ...

  3. 2018-2019 ACM-ICPC Brazil Subregional Programming Contest F. Music Festival

    题目:https://codeforces.com/gym/101908/problem/F 题意:给你n个舞台,每个舞台有很多个节目,每个节目有个开始时间,结束时间,价值,每个舞台至少出演过一个节目 ...

  4. Visual Studio Code 做PHP开发

    Visual Studio Code 做PHP开发 1. 在Windows 10环境下安装PHP: 1. 下载自己中意的PHP版本:http://windows.php.net/download (我 ...

  5. 高精度模板 洛谷Luogu P1932 A+B & A-B & A*B & A/B Problem

    P1932 A+B & A-B & A*B & A/B Problem 题目背景 这个题目很新颖吧!!! 题目描述 求A.B的和差积商余! 输入输出格式 输入格式: 两个数两行 ...

  6. .net core linux的守护进程 supervisor

    这个介绍的很全面,对初学者来说可以有更好的认识: https://www.cnblogs.com/savorboard/p/dotnetcore-supervisor.html

  7. jmeter添加自定义扩展函数之MD5加密

    1,打开eclipse,新建maven工程,在pom中引用jmeter核心jar包,具体请看---https://www.cnblogs.com/guanyf/p/10863033.html---,这 ...

  8. delphi 在代码中 添加 TO-DO 并且 管理

    TO-DO List是一项非常好用的功能.采用她可以让我们很清楚的了解以前完成了那些任务,还有哪些任务需要做,由谁负责完成,是不是比较紧急的任务等.今天来不及完成的,明天上班就可以很快的找到任务所在的 ...

  9. Nginx允许跨域访问的配置问题

    网站项目动静分离,静态资源服务器A 业务服务器B  B中静态资源由A加载 出现如下问题: @font-face { font-family: 'iconfont'; src: url('../font ...

  10. <读书笔记>JavaScript系列之7种创建对象(面向对象)

    写在前面: 以下三选一: 阅读博文JavaScript 对象详解. 阅读<JavaScript权威指南>第6章. 阅读<JavaScript高级程序设计>第6章. 注意:只需要 ...