GCC生成静态库和动态库

本文详细介绍了Linux环境下静态库和动态库的概念、区别,以及如何生成和使用它们。通过实例演示了创建静态库.a和动态库.so的步骤,包括编译、链接和执行。此外,还探讨了GCC编译器的工作原理,涉及预处理、编译、汇编和链接等阶段。最后,分析了静态库与动态库的优缺点和大小差异,强调了它们在程序开发中的应用选择。
摘要由CSDN通过智能技术生成

一、简述

库(Library)是由系统提供的一组具有特定功能的函数的集合,是为提高开发效率和运行效率而设计的。从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。

  • 1.1 静态库

静态是指每个用到该库的应用程序都会将该库拷贝到自己的目标代码中,程序运行是则不需要静态库的存在,因此利用静态库生成的文件比较大。
命名:lib****.a

  • 1.2 动态库

动态库又名共享库,在编译时并没有将该库带进目标代码中,当程序执行到相关函数时才调用该库,因此使用这类库生成的文件会比较小。动态库的改变并不会影响你自己的程序,因此动态库的升级比较方便。除此之外,动态库有一个最显著的特点就是:当多个程序调用同个共享库时,内存中只有一个动态库实例。
命名: lib****.so

  • 1.3 二者区别

前者是编译连接的,后者是程序运行载入的。
linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。

二、库的生成

实例使用:

2.1 创建一个目录

在这里插入图片描述

mkdir:用于创建指定名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前已有的目录;
ls:用来打印当前目录清单或者打印出指定目录下的文件及文件清单。ls命令在打印文件清单时,还可以查看文件权限、目录信息等等;

2.2 hello代码

“hello.c”

#include"hello.h"
#include<stdio.h>
 
void hello(const char *name)
{
	  printf("hello %s\n",name);
}

“hello.h”

#ifndef HELLO_H_
#define HELLO_H_
 
void hello(const char *name);
 
#endif

“main.c”

#include "hello.h"
int main()
{
	  hello("world!");
	  return 0;
}

2.3 gcc编译得到.o文件

在这里插入图片描述

cd:进入目录文件;
gedit:进入文本编辑器;
ls:查看当前目录下的子文件

三、创建静态库

3.1 生成静态库.a文件

静态库文件命名规范:以lib作为前缀,是.a文件
ar -crv libmyhello.a hello.o
在这里插入图片描述

3.2 程序中使用静态库

方式一:
gcc -o hello main.c -L. -lmyhello
在这里插入图片描述
方式二:
gcc main.c libmyhello.a -o hello
在这里插入图片描述
方式三:
先生成main.o,再生成可执行文件libmyhello.a
gcc -c main.c 或
gcc -o hello main.c libmyhello.a
在这里插入图片描述

3.3 验证静态库的特点

在删掉静态库的情况下,运行可执行文件,发现程序仍旧正常运行,表明静态库跟程序执行没有联系。同时,也表明静态库是在程序编译的时候被连接到代码中的。
在这里插入图片描述

四、创建动态库

4.1 生成动态库.so文件

动态库文件命名规范:以lib作为前缀,是.so文件

gcc -shared -fPIC -o libmyhello.so hello.o
在这里插入图片描述

shared:表示指定生成动态链接库,不可省略;
-fPIC:表示编译为位置独立的代码,不可省略;

4.2 程序中执行动态库

gcc -o hello main.c -L. -lmyhello 或
gcc main.c libmyhello.so -o hello

再运行可执行文件hello,会出现错误
在这里插入图片描述
解决方法:将libmyhello.so复制到目录/usr/lib中。由于运行时,是在/usr/lib中找库文件的。
sudo mv libmyhello.so /usrr/lib
在这里插入图片描述

mv:将源文件重命名为目标文件, 或将源文件移动至指定目录;
注意:在拷贝文件时,需要是root用户才可执行,在命令前加上sudo,表示允许普通用户使用超级用户权限;
且如若第一次使用 root 用户,要先激活,使用命令:sudo passwd root 然后连续输入两个密码即可

4.3 静态库与动态库的比较

1、首先都是通过gcc编译得到.o文件
gcc -c hello.c

2、创建命令不同:
a) 创建静态库
ar -crv libmyhello.a hello.o

b) 创建动态库
gcc -shared -fPIC -o libmyhello.so hello.o
移动子目录
sudo mv libmyhello.so /usrr/lib

3、最后生成可执行文件并执行
gcc -o hello main.c -L. -lmyhello
./hello

五、实例使用库

5.1 实例一

1、代码准备:
A1.c

#include<stdio.h>
void print1(int arg)
{
	printf("A1 print arg:%d\n",arg);
}

A2.c

#include<stdio.h>
void print2(char *arg)
{
	printf("A2 printf arg:%s\n",arg);
}

A.h

#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif

test.c

#include<stdio.h>
#include"A.h"
int main()
{
	print1(1);
	print2("test");
	exit(0);
}

操作过程如前面所示
gcc -c A1.c A2.c

在这里插入图片描述
2、使用静态库

ar crv libfile.a A1.o A2.o
gcc -o test test.c libfile.a

在这里插入图片描述
3、使用动态库

gcc -shared -fPIC -o libfile.so A1.o A2.o
gcc -o test test.c libfile.so

在这里插入图片描述

5.2 实例二

1、代码准备:
sub1.c

float x2x(int a,int b)
{
	float c=0;
	c=a+b;
	return c;
}

sub2.c

float x2y(int a,int b)
{
	float c=0;
	c=a/b;
	return c;
}

sub.j

#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif

main.c

#include<stdio.h>
#include"sub.h"
void main()
{
	int a,b;
	printf("Please input the value of a:");
	scanf("%d",&a);
	printf("Please input the value of b:");
	scanf("%d",&b);
	printf("a+b=%.2f\n",x2x(a,b));
	printf("a/b=%.2f\n",x2y(a,b));
}

操作依旧如上:
mkdir homework3
gcc -c sub1.c sub2.c

2、静态库:
ar crv libsub.a sub1.o sub2.o
gcc -o main main.c libsub.a

在这里插入图片描述

在这里插入图片描述
3、动态库:
gcc -shared -fPIC libsub.so sub1.o sub2.o
gcc -o main main.c libsub.so
sudo mv libsub.so /usr/lib

在这里插入图片描述
4、静态库与动态库的大小比较
在这里插入图片描述
在这里插入图片描述

对比发现静态库比动态库小得多,且静态库生成的可执行文件会比动态库的稍微大点。

六、探究GCC编译器背后的故事

6.1引言

GCC(GNU C Compiler)是编译工具,其背后有多个编辑器和工具,分别介绍如下:

addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对 应的源代码位置。
as:主要用于汇编。
ld:主要用于链接。
ar:主要用于创建静态库。
ldd:可以用于查看一个可执行程序依赖的共享库。
objcopy:将一种对象文件翻译成另一种格式,譬如将 .bin 转换成 .elf 或者将.elf 转换成.bin 等。
objdump:主要的作用是反汇编。
readelf:显示有关 ELF 文件的信息。
size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小 等。

6.2 用gcc编译

1、新建一个helloworld.c文件

gedit helloworld.c

helloworld.c

#include <stdio.h>
int main(void){
	printf("Hello World!\n");
	return 0;
}

2、预处理

预处理的工作:
a. 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编 译指令,比如#if #ifdef #elif #else #endif 等。
b. 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
c. 删除所有注释“//”和“/* */”。
d. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
e. 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。

预处理命令:
gcc -E helloworld.c -o helloworld.i

3、编译

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

gcc -S helloworld.i -o helloworld.s

4、汇编

汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o 的目标文件中。

gcc -c helloworld.s -o helloworld.o
或 as -c helloworld.s -o helloworld.o

5、链接
将 .o 文件链接生成可执行文件,并执行程序。
gcc helloworld.o -o helloworld
./helloworld

注:该链接语句默认链接动态库,如果要链接静态库,使用命令:
gcc -static helloworld.c -o helloworld

在这里插入图片描述

6.3 结果对比查看

1、查看文件大小
size helloworld
查看有无链接动态库文件
ldd helloworld

在这里插入图片描述

2、将静态库加入到可执行文件中

gcc -static helloworld.c -o helloworld  
./helloworld  
sieze helloworld  
ldd helloworld  

在这里插入图片描述

结果显示无链接动态库文件,并且通过对比前后动态库及静态库文件大小,发现后面生成的静态库比动态库大得多!

七、总结

通过学习用gcc生成静态库与动态库,不仅能够从底层理解代码的执行过程,而且更能熟悉在Ubuntu里的一些具体操作命令。在实操过程中,经常会有各种或大或小的问题,通过搜索基本能够在论坛上找到解决方法。
————————————————————————

参考资料

1、gcc生成静态库.a和动态库.so
2、揭开gcc编辑器的面貌

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值