c语言从上学到上班 (1)静态链接

本文详细介绍了静态链接的基本原理,包括声明与定义的作用、重定位过程及编译选项等内容,并通过实例展示了如何解决静态链接中常见的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

连接学习与工作

背景

现在商用软件规模以几十万行代码计,因此我们会多人协作,分别开发不同的模块。在后续维护过程中也会针对模块查找问题,不至于迷失在大量的代码当中。静态链接就是一个简单的解耦手段。这一篇我们只围绕静态链接讨论,动态链接放在后续讨论。

原理

拼图
每个人负责一个小块,同时把这一小块的对外接口提供出来,最终组装成一个完整拼图。这个组装的过程,就是静态链接的过程。

声明的作用

add.h

#ifndef __ADD_H
#define __ADD_H

int add(int a, int b);

#endif

main.c

#include <stdio.h>

int main(int argc, char* argv[])
{
    int a = 1, b = 2;
    int result = add(a, b);
    printf("%d", result);
    return 0;
}

g++ -c main.c add.h
main.c: In function ‘int main(int, char**)’:
main.c:6:23: error: ‘add’ was not declared in this scope

如果没有在使用add函数之前声明int add(int a, int b)就会报找不到add这个符号。

接下来,在main.c当中加上#include “add.h”,在预处理阶段,头文件展开,就会把int add(int a, int b)的声明放到main.c当中。

#include <stdio.h>
#include "add.h"
int main(int argc, char* argv[])
{
    int a = 1, b = 2;
    int result = add(a, b);
    printf("%d", result);
    return 0;
}

g++ -c main.c add.h
编译成功,生成main.o(目标文件,非可执行文件,因为还没有链接)

Q: 为什么我的add函数根本没有实现,它还会编译通过呢?
A: 编译过程中我们的输入时main.c和add.h,输出是main.o。声明的作用就是add.h告诉编译器,我提供了一个int add(int a, int b)的函数给其他人使用。编译器知道了之后,在编译main.c过程中发现add没有在main.c定义,就会把add标记为一个外部符号,放到自己的重定位表当中(重定位表在main.o里面存着),而不会报错。

objdump -r main.o
main.o: file format elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000028 R_X86_64_PC32 _Z3addii0x0000000000000004
0000000000000035 R_X86_64_32 .rodata
000000000000003f R_X86_64_PC32 printf0x0000000000000004

RELOCATION RECORDS FOR [.eh_frame]:
OFFSET TYPE VALUE
0000000000000020 R_X86_64_PC32 .text

重定位表中包含有add和printf两个函数的记录。

readelf -s main.o
readelf

_Z3addii,printf的Ndx为undefine,说明这两个符号在main.o中为未定义的。

定义的作用

g++ main.o add.h -o main
main.o: In function ‘main’:
main.c:(.text+0x28): undefined reference to ‘add(int, int)’
collect2: error: ld returned 1 exit status

从上图中,我们知道main.o里面有两个函数_Z3addii,printf为undefine的。这里报的错误是未定义的add函数。printf在#include

#include "add.h"

int add(int a, int b)
{
    return a + b;
}

g++ -c add.c
g++ main.o add.o -o main

Q:重定位是怎么做的呢?
A:add.o当中add这个函数在代码段中的相对位置已经确定(设为addressA),现在把add.o和main.o合并成了main(可执行文件),那么add的代码段在main当中的位置就发生了偏移(设偏移地址为x),但是add函数相对于add的代码段的位置没有变化,因此add函数的地址就变成了(x+addressA)。
main.o中使用了add函数,使用的位置(比如是address1,address2)在main.o的重定位表当中存储着,那么随着main.o合并入main,其代码段偏移量为(x),那么使用add函数的位置就变成了(x+address1,x+address2),此时编译器会把这两个位置的add函数的地址替换成x+addressA。这样main在执行过程中就能正确找到add函数的位置了。

编译选项

头文件搜索路径

把add.h搬到上一级目录(不要是系统默认的include path就行)

g++ -c main.c
main.c:2:17: fatal error: add.h: No such file or directory

找不到add.h

g++ -c main.c -I../

-I用于指定除默认头文件搜索路径之外的头文件搜索路径。

静态链接库

请看:http://blog.youkuaiyun.com/xuhongning/article/details/6365200

ar rcs libadd.a add.o
g++ main.o libadd.a -o main

编译完成之后,即使把libadd.a删除,也不会影响main执行。因为libadd.a会被合成到main当中。因此在实际应用中,静态库中的.o通常是只有几个函数的小文件的集合,而非全部函数的一个大文件,这样可以保证生成的可执行文件比较小。

总结

想深入了解,则需要结合《程序员的自我修养》这本书,再在实战过程中需要大量的反编译才能真正了解每一个字节。但我认为没有必要,因为实际情况是,我会用别人提供的库,会给别人提供正确的库,就已足够,没必要纠结ELF哪个字段是哪个含义,太学究了。

本文主要说明了在静态链接中,声明的作用,定义的作用,以及由于误用导致的错误,为实际工作中遇到编译链接失败提供一个指向型的分析方法。

个人理解,欢迎拍砖
目标文件也好,可执行文件也好,都是我们编写的代码的另一种表现形式。因此它们不会涉及到堆栈的分配等问题。比如代码中写了int *a = new int[10]。那它会在代码段中把这个new的指令保存下来,而不会在可执行文件中开辟10个整形空间。因为ELF,.o也没有堆栈这个东西。堆栈只有在运行时,执行指令的时候,才会去运用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值