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

news2024/12/23 16:40:37

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

文章目录

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

一、GCC编译过程

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

1. 预处理阶段

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

示例代码:

// main.c
#include <stdio.h>

#define PI 3.1415926

int 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   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movsd   .LC0(%rip), %xmm0
    movl    $1, %eax
    movl    $.LC1, %edi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    leave
    ret
    .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_H

void 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.coloradmin.cn/o/846088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】sql字段约束

在MySQL中&#xff0c;我们需要存储的数据在特定的场景中需要不同的约束。当新插入的数据违背了该字段的约束字段&#xff0c;MySQL会直接禁止插入。 数据类型也是一种约束&#xff0c;但数据类型这个约束太过单一&#xff1b;比如我需要存储的是一个序号&#xff0c;那就不可…

SpringBoot第33讲:SpringBoot集成ShardingJDBC - 基于JPA的读写分离

SpringBoot第33讲&#xff1a;SpringBoot集成ShardingJDBC - 基于JPA的读写分离 本文是SpringBoot第33讲&#xff0c;主要介绍分表分库&#xff0c;以及SpringBoot集成基于 ShardingJDBC 的读写分离实践 文章目录 SpringBoot第33讲&#xff1a;SpringBoot集成ShardingJDBC - 基…

开能转债,大叶转债上市价格预测

开能转债 基本信息 转债名称&#xff1a;开能转债&#xff0c;评级&#xff1a;A&#xff0c;发行规模&#xff1a;2.5亿元。 正股名称&#xff1a;开能健康&#xff0c;今日收盘价&#xff1a;5.6元&#xff0c;转股价格&#xff1a;5.67元。 当前转股价值 转债面值 / 转股价…

第八次作业

1、什么是数据认证&#xff0c;有什么作用&#xff0c;有哪些实现的技术手段&#xff1f; 数据认证的官方回答&#xff1a;数字认证证书它是以数字证书为核心的加密技术可以对网络上传输的信息进行加密和解密、数字签名和签名验证&#xff0c;确保网上传递信息的安全性、完整性…

Spring Boot2.xx开启监控 Actuator

spring boot actuator介绍 Spring Boot包含许多其他功能&#xff0c;可帮助您在将应用程序推送到生产环境时监视和管理应用程序。 您可以选择使用HTTP端点或JMX来管理和监视应用程序。 审核&#xff0c;运行状况和指标收集也可以自动应用于您的应用程序。 总之Spring Boot Ac…

设计模式——六大设计原则详解

什么是设计模式 随着编程的发展&#xff0c;程序员们发现再解决一些普遍的问题的时候&#xff0c;所使用的解决方案是大体相同的。这些解决方法是众多程序员经过长时间的实践和试错最终总结出来了。所有就有人将它们总结起来形成了设计模式。设计模式出现的意义是为了重用代码&…

玩转Pandas_TA:一站式掌握技术分析指标

01 引言 Pandas_TA —— 一个结合了pandas的强大数据处理能力与技术分析的库&#xff0c;旨在为金融市场分析师和交易者提供一个简单、高效的工具集&#xff0c;从而帮助他们更容易地在数据集上应用各种技术分析指标。pandas_ta为用户提供了直接在DataFrame上运行技术指标计算的…

gitlab 503 错误的解决方案

首先使用 sudo gitlab-ctl status 命令查看哪些服务没用启动 sudo gitlab-ctl status 再用 gitlab-rake gitlab:check 命令检查 gitlab。根据发生的错误一步一步纠正。 gitlab-rake gitlab:check 查看日志 tail /var/log/gitlab/gitaly/current删除gitaly.pid rm /var/opt…

Go的任务调度单元与并发编程

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 本文主要介绍Go语言、进程、线程、协程的出现背景原因以及Go 语言如何解决协程的…

Idea添加mybatis的mapper文件模版

针对Java开发人员&#xff0c;各种框架的配置模版的确是需要随时保留一份&#xff0c;在使用的时候&#xff0c;方便复制粘贴&#xff0c;但是也依然不方便&#xff0c;我们可以给开发工具&#xff08;IDE&#xff09;中添加配置模版&#xff0c;这里我介绍下使用idea开发工具&…

安达发|APS排程软件与某一知名APS软件整体对比

APS排程软件作为高级计划与排程系统。主要是解决小批量、多品种的复杂生产要求。客户的要求交期越来越准。由于成本不断攀升&#xff0c;所以高产出和低成本也是重要的考量因素。 下面我们就安达发APS软件与某一知名APS做一下整体对比&#xff1a; 1.功能实用性 安达发APS排…

记录--前端重新部署如何通知用户

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 1. 场景 前端构建完上线&#xff0c;用户还停留还在老页面&#xff0c;用户不知道网页重新部署了&#xff0c;跳转页面的时候有时候js连接hash变了导致报错跳不过去&#xff0c;并且用户体验不到新功能…

springboot 对接 minio 分布式文件系统

1. minio介绍 Minio 是一个基于Go语言的对象存储服务。它实现了大部分亚马逊S3云存储服务接口&#xff0c;可以看做是是S3的开源版本&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象…

计算机视觉大牛Liang-Chieh Chen从谷歌离职!加入字节跳动!

点击下方卡片&#xff0c;关注“CVer”公众号 AI/CV重磅干货&#xff0c;第一时间送达 点击进入—>【图像分割和论文投稿】交流群 这应该是国内首次报道&#xff01; Amusi 发现&#xff1a;Liang-Chieh Chen 巨佬在官网宣布&#xff1a;已离职谷歌&#xff0c;目前在字节跳…

以太网Ethernet通信协议

一、以太网简介 计算机网络可分为局域网(LAN)、 城域网(MAN)、广域网(WAN)、互联网(Initernet)。局域网按传输介质所使用的访问控制方法可分为&#xff1a;以太网(Ethernet)、光纤分布式数据接口(FDDI)、异步传输模式(ATM)、令牌环网(Token Ring)、交换网(Switching) 等&#x…

【JavaSE】什么是抽象类?什么是内部类?以及它们的作用是什么?

这篇文章我们主要学习的是两个知识点&#xff0c;可以来解决文章标题所提出来的三个问题。 目录 1.抽象类 1.1 抽象类概念 1.2 抽象类语法 1.3 抽象类特性 1.4 抽象类的作用 2.内部类 2.1 内部类的分类 2.2 实例内部类 2.3 静态内部类 2.4 匿名内部类 2.5 局部内部类…

标准化归一化 batch norm, layer norm, group norm, instance norm

Layer Normalization - EXPLAINED (in Transformer Neural Networks) Layer Normalization - EXPLAINED (in Transformer Neural Networks) 0~4min:什么是multi-head attention 5~7min:layer norm图示 7~9min:公式举例layer norm 9:54-end:layer norm的代码示例 group n…

2023金九银十软件测试面试题(800道)

今年你的目标是拿下大厂offer&#xff1f;还是多少万年薪&#xff1f;其实这些都离不开日积月累的过程。 为此我特意整理出一份&#xff08;超详细笔记/面试题&#xff09;它几乎涵盖了所有的测试开发技术栈&#xff0c;非常珍贵&#xff0c;人手一份 肝完进大厂 妥妥的&#…

时序预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆神经网络时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆…