【Linux操作系统】GCC编译与静态库、动态库制作详解

news/2024/12/22 10:53:12/

GCC是一款广泛使用的开源编译器,它支持多种编程语言,并且具有强大的编译能力。在软件开发中,我们经常需要将代码编译成可执行文件或者库文件。本文将详细介绍GCC编译过程以及如何制作静态库和动态库。
在这里插入图片描述

文章目录

    • 一、GCC编译过程
      • 1. 预处理阶段
      • 2. 编译阶段
      • 3. 汇编阶段
      • 4. 链接阶段
    • 二、静态库制作
      • 1. 静态库制作
      • 2. 使用静态库
      • 补充:头文件对应
    • 三、动态库制作
      • 1. 动态库制作
      • 2. 使用动态库
      • 补充:动态库加载错误及解决方法
    • 四、总结
  • 补充:gcc使用技巧

一、GCC编译过程

GCC编译过程主要分为四个阶段:预处理、编译、汇编和链接。下面我们将逐一介绍每个阶段的作用。
在这里插入图片描述

1. 预处理阶段

预处理阶段主要是对源代码进行宏展开、头文件包含、条件编译等预处理操作。预处理器会根据源文件中的预处理指令,生成一个新的文件,通常以.i作为扩展名。

示例代码:

// main.c
#include <stdio.h>#define PI 3.1415926int main() {printf("PI = %f\n", PI);return 0;
}

预处理后的代码:

// main.i
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
...

2. 编译阶段

编译阶段将预处理后的代码转换成汇编代码,即将高级语言代码翻译成汇编语言代码。编译器会检查语法错误、类型错误等,并生成一个汇编文件,通常以.s作为扩展名。

示例代码:

// main.i
# 1 "main.c"
...
int main() {printf("PI = %f\n", PI);return 0;
}

编译后的汇编代码:

// main.s.file "main.c".section    .rodata
.LC0:.string "PI = %f\n".text
.globl main.type   main, @function
main:pushq   %rbpmovq    %rsp, %rbpsubq    $16, %rspmovsd   .LC0(%rip), %xmm0movl    $1, %eaxmovl    $.LC1, %edimovl    $0, %eaxcall    printfmovl    $0, %eaxleaveret.size   main, .-main.section    .rodata
.LC1:.string "PI = %f\n".text.section    .note.GNU-stack,"",@progbits

3. 汇编阶段

汇编阶段将汇编代码转换成机器代码。汇编器会将汇编代码转换成二进制指令,生成一个目标文件,通常以.o作为扩展名。

示例代码:

// main.s.file "main.c"
...

汇编后的目标文件:

// main.o
...

4. 链接阶段

链接阶段将目标文件与所需的库文件进行链接,生成最终的可执行文件。链接器会解析目标文件中的符号引用,并将其与库文件中的符号定义进行匹配。

示例代码:

// main.o
...

链接后的可执行文件:

// main
...

二、静态库制作

好的,下面我将给出一个更详细的例子来说明如何制作和使用静态库。

1. 静态库制作

首先,我们需要创建两个源文件add.c和sub.c,分别实现加法和减法的功能。

add.c:

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

sub.c:

int sub(int a, int b) {return a - b;
}

然后,我们使用gcc命令将这两个源文件编译成目标文件。

$ gcc -c add.c sub.c

生成add.o和sub.o文件。

接下来,我们使用ar命令将目标文件打包成一个静态库文件libmath.a。

$ ar rcs libmath.a add.o sub.o

这一步完之后你的目录里会包含
add.c /sub.c /add.o /sub.o /libmath.a

2. 使用静态库

现在我们已经制作好了一个静态库libmath.a,接下来我们将使用这个静态库。

首先,我们创建一个main.c文件,调用静态库中的函数。

main.c:

#include <stdio.h>extern int add(int a, int b);
extern int sub(int a, int b);int main() {int a = 10;int b = 5;int sum = add(a, b);int difference = sub(a, b);printf("Sum: %d\n", sum);printf("Difference: %d\n", difference);return 0;
}

要进行声明函数,要不系统会默认隐形声明,容易导致错误。

然后,我们使用gcc命令将main.c文件与静态库链接起来生成可执行文件。

$ gcc main.c -L. -lmath -o main

最后,我们运行可执行文件main。

$ ./main

输出结果:

Sum: 15
Difference: 5

补充:头文件对应

假设我们有一个名为example的静态库,其中包含了一个函数void print_hello()。为了使用这个函数,我们需要有一个头文件example.h,其中包含了函数的声明。

example.h:

#ifndef EXAMPLE_H
#define EXAMPLE_Hvoid print_hello();#endif

在使用这个静态库的源文件中,我们需要包含example.h头文件,并调用其中的函数。

main.c:

#include <stdio.h>
#include "example.h"int main() {printf("Hello, world!\n");print_hello();return 0;
}

在编译时,我们需要指定静态库文件的搜索路径和要链接的库文件。假设libexample.a是我们的静态库文件,可以使用以下命令进行编译:

gcc -o program main.c -L/path/to/library -lexample

其中-L/path/to/library指定了静态库文件的搜索路径,-lexample指定要链接的静态库。

总结一下:首先,我们需要将多个目标文件打包成一个静态库文件,然后在编译时指定静态库的路径和名称。最后,我们可以通过调用静态库中的函数来使用其中的功能。希望这个例子能够帮助你更好地理解静态库的制作和使用过程。

三、动态库制作

动态库是在程序运行时被加载的库文件,它可以被多个程序共享使用,减少了内存的占用。

1. 动态库制作

首先,我们需要创建两个源文件add.c和sub.c,分别实现加法和减法的功能。

add.c:

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

sub.c:

int sub(int a, int b) {return a - b;
}

然后,我们使用gcc命令将这两个源文件编译成目标文件,并使用-fPIC选项生成位置无关的代码。

$ gcc -c -fPIC add.c sub.c

同样生成add.o和sub.o文件。

接下来,我们使用gcc命令将目标文件打包成一个动态库文件libmath.so。

$ gcc -shared -o libmath.so add.o sub.o

2. 使用动态库

现在我们已经制作好了一个动态库libmath.so,接下来我们将使用这个动态库。

首先,我们创建一个main.c文件,调用动态库中的函数。

main.c:

#include <stdio.h>
#include <dlfcn.h>int main() {void* handle = dlopen("./libmath.so", RTLD_LAZY);if (!handle) {fprintf(stderr, "Failed to open library: %s\n", dlerror());return 1;}int (*add)(int, int) = dlsym(handle, "add");int (*sub)(int, int) = dlsym(handle, "sub");int a = 10;int b = 5;int sum = add(a, b);int difference = sub(a, b);printf("Sum: %d\n", sum);printf("Difference: %d\n", difference);dlclose(handle);return 0;
}
/*
首先,在main函数中,我们声明了一个void指针变量handle,用于存储打开动态库后返回的句柄。然后,我们使用dlopen函数打开动态库文件libmath.so,指定RTLD_LAZY标志表示在需要时才解析符号。如果打开动态库失败,我们使用dlerror函数获取错误信息并打印到stderr流上,然后返回1表示出错。接下来,我们使用dlsym函数获取动态库中的函数指针。dlsym函数的第一个参数是动态库的句柄,第二个参数是要获取的函数名。我们使用函数指针的方式来声明和初始化两个函数指针变量add和sub,分别指向动态库中的add函数和sub函数。然后,我们定义了两个整型变量a和b,并分别赋值为10和5。接下来,我们通过调用函数指针变量add和sub来调用动态库中的函数,得到加法和减法的结果,并分别赋值给sum和difference变量。最后,我们使用dlclose函数关闭动态库句柄,释放资源。*/

然后,我们使用gcc命令将main.c文件与动态库链接起来生成可执行文件,并指定动态库的路径和名称。

$ gcc main.c -L. -ldl -o main

最后,我们运行可执行文件main。

$ ./main

输出结果:

Sum: 15
Difference: 5

总结一下:首先,我们需要将多个目标文件编译成位置无关的代码,并使用gcc命令将它们打包成一个动态库文件。然后,在使用动态库的程序中,我们需要使用dlopen函数打开动态库,并使用dlsym函数获取动态库中的函数指针。最后,我们可以通过调用动态库中的函数来使用其中的功能。

补充:动态库加载错误及解决方法

如果动态库路径错误,可以按照以下方法解决:

  1. 检查动态库文件的路径是否正确:确保指定的路径是动态库文件所在的准确路径。
  2. 使用绝对路径或相对路径:可以使用绝对路径来指定动态库的路径,例如/path/to/library/libexample.so。或者,使用相对路径来指定动态库的路径,相对路径是相对于当前工作目录的路径,例如./libexample.so
  3. 设置LD_LIBRARY_PATH环境变量:可以通过设置LD_LIBRARY_PATH环境变量来指定动态库的搜索路径。例如,如果动态库文件在/path/to/library目录中,可以执行以下命令:
    export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH
    
    这将把/path/to/library添加到动态库的搜索路径中。
  4. 使用rpath选项:可以在链接时使用-rpath选项来指定动态库的搜索路径。例如,使用以下命令来编译和链接程序:
    gcc -o program main.c -L/path/to/library -Wl,-rpath=/path/to/library -lexample
    
    这将在程序中设置动态库的搜索路径为/path/to/library

通过以上方法,您可以解决动态库路径错误的问题。请注意,在使用LD_LIBRARY_PATH环境变量或rpath选项时,确保指定的路径是正确的,并且动态库文件存在于该路径中。

四、总结

本文详细介绍了GCC编译过程以及如何制作静态库和动态库。通过预处理、编译、汇编和链接四个阶段,我们可以将源代码转换成可执行文件或者库文件。静态库将多个目标文件打包成一个文件,程序在编译时会将静态库的代码复制到可执行文件中;而动态库是在程序运行时被加载的库文件,它可以被多个程序共享使用,减少了内存的占用。
内容补充:


补充:gcc使用技巧

gcc是GNU Compiler Collection(GNU编译器套件)的缩写,是一个广泛使用的编程语言编译器。它支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada和Go等。下面是gcc的常用参数和其作用的简要说明:

  • -c:只编译源文件,生成目标文件(.o文件),不进行链接操作。
  • -o:指定输出文件的名称。
  • -I:指定头文件的搜索路径。
  • -L:指定库文件的搜索路径。
  • -l:链接时使用的库文件。
  • -g:生成调试信息,用于调试程序。
  • -Wall:开启所有警告信息。
  • -Werror:将警告视为错误。
  • -std:指定使用的C或C++标准。
  • -O:优化级别,包括-O0(无优化)、-O1(基本优化)、-O2(更多优化)和-O3(最大优化)等。
  • -shared:生成一个共享库文件(动态库)。
  • -fPIC:生成位置无关的代码,用于生成动态库。
  • -pthread:链接多线程库。

http://www.ppmy.cn/news/1015457.html

相关文章

[国产MCU]-BL602开发实例-ADC数据采样

ADC数据采样 文章目录 ADC数据采样1、ADC介绍2、ADC驱动API3、ADC使用示例模数转换器(analog-to-digital converter,通常称为ADC)是一种模拟与数字转换器,支持12路外部模拟输入和若干内部模拟信号选择。 BL602的ADC支持以下四种模式:单次单通道转换、连续单通道转换、单次…

在 Linux 上以 All-in-One 模式安装 KubeSphere

官方文档&#xff1a;https://www.kubesphere.io/zh/docs/v3.3/quick-start/all-in-one-on-linux/ 操作系统 最低配置 Ubuntu&#xff1a; 16.04,18.04, 20.04, 22.04 2 核 CPU&#xff0c;4 GB 内存&#xff0c;40 GB 磁盘空间Debian Buste&#xff1a;Stretch 2 核 CPU&am…

Docker基本使用

查看本地镜像 查看本地&#xff1a;docker imagesPull镜像&#xff1a;docker pull nginx:latest登录镜像&#xff1a;docker login hub.docker.com -u **** -p ****制作镜像&#xff1a;docker build -t xxxx:v1push&#xff1a;docker push xxx:v1删除镜像:docker rmi #imag…

MyCat管理及监控——zookeeper及MyCat-web安装

1.MyCat管理 2.MyCat-eye 3.zookeeper安装 第一步&#xff1a;解压 第二部&#xff1a; 切换目录&#xff0c;创建data文件夹 第三步&#xff1a;修改zookeeper配置文件 这样zookeeper安装及配置就完成了 4.MyCat-web安装 注意mycat-web要与zookeeper关联&#xff0c;…

篇六:适配器模式:让不兼容变兼容

篇六&#xff1a;“适配器模式&#xff1a;让不兼容变兼容” 开始本篇文章之前先推荐一个好用的学习工具&#xff0c;AIRIght&#xff0c;借助于AI助手工具&#xff0c;学习事半功倍。欢迎访问&#xff1a;http://airight.fun/ 另外有2本不错的关于设计模式的资料&#xff0c…

Parquet存储的数据模型以及文件格式

文章目录 数据模型Parquet 的原子类型Parquet 的逻辑类型嵌套编码 Parquet文件格式 本文主要参考文献&#xff1a;Tom White. Hadoop权威指南. 第4版. 清华大学出版社, 2017.pages 363. Aapche Parquet是一种能有效存储嵌套数据的列式存储格式&#xff0c;在Spark中应用较多。 …

H. HEX-A-GONE Trails 2023“钉耙编程”中国大学生算法设计超级联赛(7)hdu7354

Problem - 7354 题目大意&#xff1a;有一棵n个点的树&#xff0c;A和B分别从点x&#xff0c;y开始&#xff0c;每轮可以移动到一个相邻节点&#xff0c;但如果某个节点有人访问过&#xff0c;则两人都不能访问那个节点&#xff0c;先没有点可走的人输&#xff0c;问A有没有必…

JS中DOM及增删改查

1.使用DOM获取元素 1.getElementById(id) 通过元素id名获取元素 例&#xff1a;获取id名为box的元素 document.getElementById(“box”); 2.getElementsByTagName 通过标签名获取元素列表&#xff08;数组&#xff09; 例&#xff1a;获取所有的li标签 document.getElement…