计算机学习之链接

编译器驱动程序和静态链接

先来看一个实例:

main.c

/* main.c */
/* $begin main */
int sum(int *a, int n);

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

int main() 
{
    int val = sum(array, 2);
    return val;
}
/* $end main */

sum.c

/* sum.c */
/* $begin sum */
int sum(int *a, int n)
{
    int i, s = 0;
    
    for (i = 0; i < n; i++) { 
        s += a[i];
    }
    return s;
}        
/* $end sum */
代码分析
  • 这个示例程序由两个源文件组成,main.c和sum.c。main函数初始化一个整数数组,然后调用sum函数来对数组元素求和。
编译运行
  • 想要编译运行此程序,我们需要在shell中输入以下命令来调用GCC驱动程序:
    在这里插入图片描述
  • 运行可执行文件prog,则输入以下命令行:
    在这里插入图片描述
具体知识点
  • 驱动程序要将示例程序从ASCII码源文件翻译成可执行目标文件的过程中,需要经过预处理、编译、汇编、链接四个步骤。
  • 从下图中我们可以清楚看到示例程序从ASCII码源文件翻译成可执行目标文件的行为:
    在这里插入图片描述
  • 链接器为了构造可执行文件prog,必须完成两个任务:符号解析和重定位。

可重定位目标文件

  • 目标文件有三种形式:可重定位目标文件、可执行目标文件、共享目标文件。

  • 典型的ELF可重定位文件格式如下图所示:
    在这里插入图片描述

  • 可执行文件的格式如下:
    在这里插入图片描述
    图源link

  • ELF头 描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助连接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行、共享的)、机器类型(如x86-64)、节头部表的文件偏移,以及节头部表中条目的大小和数量。

  • 这里只简要介绍部分节的具体内容:
    .text:已编译程序的机器代码。
    .rodata:只读数据。
    .data:已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在.data节中,也不出现在.bss节中。
    .bss:未初始化的全局和静态C变量,在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
    .symtab:符号表

符号

  • 符号为全局变量和函数,共分为三种:全局符号、外部符号、局部符号。
  • 符号会存放在ELF的符号表中,即.symtab节中。用命令行 readelf -s symbols.o可以看到符号表中的内容,如以下示例:

symbol.c文件

#include <stdio.h>

int time;

int foo(int a) {
    int b = a + 1;
    return b;
}

int main(int argc, char *argv[])
{
    printf("%d\n", foo(5));
    return 0;
}

命令行操作结果
在这里插入图片描述

  • 符号解析:多个目标文件可能会定义相同名字的全局符号,在这种情况下,Linux链接器有一套自己的处理规则:

函数和已初始化的全局变量是强符号,未初始化全局变量是弱符号

  1. 如果有一个强符号和多个弱符号,选择强符号
  2. 如果多个弱符号同名,任意选择一个
  3. 不允许有多个强符号,否则会出现错误

这警示我们在编写程序时不要随意定义全局符号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值