Linux系统下利用gcc对各种库的使用及个软件的用途

本文详细介绍了Linux环境下使用GCC生成静态库与动态库的步骤,包括创建源文件、编译、链接等,并展示了静态库与动态库在程序中的应用实例。同时,讲解了GCC常用命令,如预处理、编译、汇编和链接,以及NASM汇编编译器的使用。此外,还探讨了C运行时库的重要性和作用,以及curses库在控制台应用程序中的应用。

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

一、可执行程序的组装练习

1.可执行程序是如何被组装的?

一个源程序到一个可执行程序的过程,分为以下四步:预编译、编译、汇编、链接。
其中,编译是主要部分,其中又分为六个部分:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化,链接中,分为静态链接和动态链接两个部分。
同样在在gcc的编译流程中通常认为也是如下四个步骤,实际上就是将命令分成4步执行
预处理,生成预编译文件(.i文件):gcc –E main.c –o main.i
编译,生成汇编代码(.s文件):gcc –S main.i –o main.s
汇编,生成目标文件(.o文件):gcc –c main.s –o main.o
链接,生成可执行文件(executable文件):gcc main.o –o main

2.用gcc生成静态库和动态库并使用

用gcc生成静态库和动态库练习

第 1 步:编辑生成例子程序 hello.h、hello.c 和 main.c
先创建一个作业目录,保存本次练习的文件。
#mkdir test1#cd test1

然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件。
在这里插入图片描述

hello.c(见程序 2)是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出"Hello XXX!"。
hello.h(见程序 1)为该函数库的头文件。
main.c(见程序 3)为测试库文件的主程序,在主程序中调用了公用函数 hello。
程序 1: hello.h

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

程序 2:hello.c

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

程序 3: main.c

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

第 2 步:将 hello.c 编译成.o 文件
无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 gcc 先编译成.o 文件。在系统提示符下键入以下命令得到 hello.o 文件。
gcc -c hello.c

我们运行 ls 命令看看是否生存了 hello.o 文件。
lshello.c hello.h hello.o main.c

在 ls 命令结果中,我们看到了 hello.o 文件,本步操作完成。
下面我们来创建静态库,并使用它。

第 3 步:由.o 文件创建静态库
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。
例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件libmyhello.a。
在这里插入图片描述

我们同样运行 ls 命令查看结果:
在这里插入图片描述
ls 命令结果中有 libmyhello.a。

第 4 步:在程序中使用静态库
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。
注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。
在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。
先生成main.o,再生成成可执行文件:
在这里插入图片描述
动态库连接时也可以这样做。

运行hello程序
在这里插入图片描述
运行成功,删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。
在这里插入图片描述
运行成功,因此可知,静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。

第 5 步:由.o 文件创建动态库文件
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。
例如:我们将创建的动态库为 myhello,则动态库文件名就是 libmyhello.so。用 gcc 来创建动态库。
在系统提示符下键入以下命令得到动态库文件 libmyhello.so。
在这里插入图片描述
此时出现问题,根据 wlzany:Linux系统中关于gcc和库函数的基础操作,得知由于我的系统是AMD64位的,所以需要在上面编译生成.o文件的时候添加 -fPIC选项
在这里插入图片描述在这里插入图片描述
得到动态库文件 libmyhello.so

第 6 步:在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我们先运行 gcc 命令生成目标文件,再运行它看看结果。
在这里插入图片描述在这里插入图片描述
其中,sudo是指以管理员执行该命令行,其目的是程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示错误而终止程序运行。我们将文件libmyhello.so 复制到目录/usr/lib 中,再运行hello程序,可以看到运行成功。

静态库.a与.so库文件的生成与使用练习

建立所需文件
先创建一个作业目录,保存本次练习的文件。

mkdir test2
#cd test2

然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的四个文件 A1.c 、 A2.c、 A.h、test.c 。
在这里插入图片描述

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 <stdlib.h>
#include "A.h"
int main(){
print1(1);
print2("test");
exit(0);
}

在这里插入图片描述
静态库.a文件的生成与使用
1.生成目标文件

gcc -c A1.c A2.c

在这里插入图片描述
2.生成静态库.a文件

ar crv libafile.a A1.o A2.o

在这里插入图片描述
在这里插入图片描述
3.使用.a 库文件,创建可执行程序并执行

gcc -o test test.c libafile.a
./test

在这里插入图片描述
共享库.so 文件的生成与使用

1.生成目标文件(xxx.o() 此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so
文件时会出错)

gcc -c -fpic A1.c A2.c

2.生成共享库.so 文件

gcc -shared *.o -o libsofile.so

3.使用.so 库文件,创建可执行程序

gcc -o test test.c libsofile.so
./test

在这里插入图片描述
出现错误

./test: error while loading shared libraries: libsofile.so: cannot open shared object file: No
such file or directory

原因是找不到对应的.so 文件。
这是由于 linux 自身系统设定的相应的设置的原因,即其只在/lib and /usr/lib 下搜索对应
的.so 文件,故需将对应 so 文件拷贝到对应路径。

sudo cp libsofile.so /usr/lib
./test

在这里插入图片描述

3.静态库与动态库应用实例

静态库应用实例

1.编写一个x2x函数,一个x2y函数(功能自定),main函数代码将调用x2x和x2y;
将这3个函数分别写成单独的3个 .c文件。
在这里插入图片描述

main.h

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

sub1.c

#include<stdio.h>
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;
}

main.c

#include<stdio.h>
#include"main.h"
void main()
{
        int a=8,b=4;
        printf("%f\n",x2x(a,b));
        printf("%f\n",x2y(a,b));
}

2.用gcc分别编译为3个.o 目标文件

gcc -c sub1.c sub2.c

3.将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件

ar crv libsub.a sub1.o sub2.o

在这里插入图片描述
4.用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序并记录文件的大小

gcc -o main main.c libsub.a
./main
ls -la

在这里插入图片描述

动态库应用实例

1.生成动态库

gcc -shared -fPIC -o libsub.so sub1.o sub2.o

2.使用.so 库文件,创建可执行程序,并运行

gcc -o main3 main.c libsub.so
./main

在这里插入图片描述
3.查看动态库生成文件的大小

ls -la

在这里插入图片描述

通过比较发现静态库要比动态库要小很多,生成的可执行文件大小也存在较小的差别。
因为静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

二、gcc常用命令及NASM汇编编译器的使用

1.Linux GCC常用命令

1.简介

GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。

2.简单的编译

对于一个简单的程序test.c来说,可以通过// gcc test.c -o test //来进行一步到位的编译。
而实际上的编译过程分为四个阶段,分别是:
预处理(Preprocessing):// gcc -E test.c -o test.i // 或 // gcc -E test.c //
编译(Compilation):// gcc -S test.i -o test.s //
汇编 (Assembly):// gcc -c test.s -o test.o //
连接(Linking):// gcc test.o -o test //

在这里插入图片描述
3.多个程序编译

对于有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并
最终生成可执行程序 test,可以使用下面这条命令:
// gcc test1.c test2.c -o test //
和单个程序的编译一样,,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深究起
来,上面这条命令大致相当于依次执行如下三条命令:
// gcc -c test1.c -o test1.o //
// gcc -c test2.c -o test2.o //
// gcc test1.o test2.o -o test //

4.检错

GCC会帮助我们找出程序中的错误,比如-pedantic 编译选项
// gcc -pedantic illcode.c -o illcode //
GCC 发现ANSI/ISO C 语言标准中要求进行编译器诊断的那些情况并提出警告。

同时还有-W开头的编译选项,如-Wall
// gcc -Wall illcode.c -o illcode //
使用它能够使 GCC 产生尽可能多的警告信息。

使用-Werror 选项, GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。
// gcc -Werror test.c -o test //

2.GCC编译器背后的故事

GCC是编译工具,但是与它共同完成编译工作的还有以下成员。

Binutils:
一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、 size 等。这 一组工具是开发和调试不可 缺少的工具 ,分别简介
如下:
(1) addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
(2) as:主要用于汇编,有关汇编的详细介绍请参见后文。
(3) ld:主要用于链接,有关链接的详细介绍请参见后文。
(4) ar:主要用于创建静态库。为了便于初学者理解,在此介绍动态库与静态库的概念:
如果要将多个 .o 目标文件生成 一个库文件,则存在两种类型的库,一种是静态库,另一种是动态库。
在 windows 中 静态 库是 以 .lib 为后缀的文件 ,共享库是以 .dll 为后缀的文件 。 在 linux 中静态库是以 .a 为后缀的文件,共享库是以 .so 为后缀的文件。
静态库和动态库的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。在 Linux 系统中,可以用 ldd 命令查看一个可执行程序依赖的共享库。
如果一个系统中存在多个需要同时运行的程序且这些程序之间存在共享库,那么采用动态库的形式将更节省内存。
(5) ldd:可以用于查看一个可执行程序依赖的共享库。
(6) objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或
者将.elf 转换成.bin 等。
(7) objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。
(8) readelf:显示有关 ELF 文件的信息,请参见后文了解更多信息。
(9) size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小
等,请参见后文了解使用 size 的具体使用实例。

C 运行库
C 语言标准主要由两部分组成:一部分描述 C 的语法,另一部分描述 C 标准库。
C 标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类
型声明和宏定义,譬如常见的 printf 函数便是一个 C 标准库函数,其原型定义
在 stdio 头文件中。
C 语言标准仅仅定义了 C 标准库函数原型,并没有提供实现。因此,C 语言编译
器通常需要一个 C 运行时库(C Run Time Libray,CRT)的支持。C 运行时库又
常简称为 C 运行库。与 C 语言类似,C++也定义了自己的标准,同时提供相关支
持库,称为 C++运行时库。

3.EFF文件格式

一个典型的 ELF 文件包含下面几个段(section):
text:已编译程序的指令代码段。
rodata:ro 代表 read only,即只读数据(譬如常数 const)。
data:已初始化的 C 程序全局变量和静态局部变量。
bss:未初始化的 C 程序全局变量和静态局部变量。
debug:调试符号表,调试器用此段的信息帮助调试。

使用 readelf -S 查看其各个 section 的信息
在这里插入图片描述
使用 objdump -D 对其进行反汇编
在这里插入图片描述

4.安装并使用nasm

1.简介
as汇编编译器针对的是AT&T汇编代码风格,Intel风格的汇编代码则可以用nasm汇编编译器编译生成执行程序。

2.在Linux系统下安装nasm汇编编译器
在这里插入图片描述
用nasm -version查看是否安装成功
在这里插入图片描述
3.创建hello.asm文件,编写程序

vim hello.asm

hello.asm:

	; hello.asm
section .data            ; 数据段声明
        msg db "Hello, world!", 0xA     ; 要输出的字符串
        len equ $ - msg                 ; 字串长度
section .text            ; 代码段声明
global _start            ; 指定入口函数
_start:                  ; 在屏幕上显示一个字符串
        mov edx, len     ; 参数三:字符串长度
        mov ecx, msg     ; 参数二:要显示的字符串
        mov ebx, 1       ; 参数一:文件描述符(stdout)
        mov eax, 4       ; 系统调用号(sys_write)
        int 0x80         ; 调用内核功能
                         ; 退出程序
        mov ebx, 0       ; 参数一:退出代码
        mov eax, 1       ; 系统调用号(sys_exit)
        int 0x80         ; 调用内核功能

4.使用nasm -f elf64 hello.asm,会生成一个hello.o文件,利用ls命令核实创建是否成功。

nasm -f elf64 hello.asm
ls

在这里插入图片描述
5.使用ld -s -o hello hello.o ,生成一个可执行文件hello,并运行hello程序。

ld -s -o hello hello.o
./hello

在这里插入图片描述

5.查看生成程序大小并与C代码的编译生成的程序对比

1.nasm输出hello world生成的程序大小:

size hello

在这里插入图片描述
2.编写C语言输出hello world的程序,并查看大小
在这里插入图片描述

对比可知,用nasm编译编译器编译出来的程序比C语言编译出的程序小很多,更节约内存。

三、背后的支持者——第三方库之光标库(curses)

1.光标库(curses)的主要函数功能及基本函数名称及功能

1.光标库(curses)的主要函数功能
curses函数库能够优化光标的移动并最小化需要对屏幕进行的刷新,从而也减少了必须向字符终端发送的字符数目。
2.光标库的几个基本函数
1.输出到屏幕:

  int addch(const chtype ch);      //当前位置add ch
  int addchstr(chtype *const str);    //当前位置add str
  int printw(char *format,...);      //类printf,格式化输出
  int refresh(void);          //刷新物理屏,更改逻辑屏,必须刷新才能在物理屏上面显示
  int box(WINDOW *ptr, chtype v_ch, chtype h_ch);    //围绕ptr指定窗口画方框,垂直/水平方向字符v_ch/h_ch
  int insch(chtype ch);                //插入ch,右移
  int insertln(void);                  //插入空白行,下移
  int delch(void);                  //删除光标位置字符,左移
  int deleteln(void)                 //删除当前行,上移
  int beep(void);                  //发声
  int flash(void);                  //闪屏

2.从屏幕读取

  chtype inch(void);      //读取光标位置字符
  int instr(char *string);    //读取字符串到str,到NUL,不总是被支持
  int innstr(char *str, int n_of_str);  //读取n个字符到str,或NUL,不总是被支持

3.清除屏幕

  int erase(void);    //清屏,以空白字符填充
  int clear(void);    //清屏
  int clrtobot(void);    //清至屏幕结尾
  int clrtoeol(void);    //清至行尾

4.移动光标

  int move(int new_y, int new_x);    //y垂直方向,x水平方向
  int leaveok(WINDOW *ptr, bool lf);  //false,刷新后,硬件光标位置同逻辑光标;true,刷新后,硬件光标随机位置。一般默认选项符合用户需求。

5.键盘输入

  int getch(void);
  int getstr(char *str);
  int getnstr(char *str, int n_of_str)
  int scanw(char *format,...);

6.移动和更新窗口

  int mvwin(WINDOW *ptr ,int new_y, int new_x);  
  int wrefresh(WINDOW *ptr);         
  int wclear(WINDOW *ptr);            
  int werase(WINDOW *ptr);
  int touchwin(WINDOW *ptr);      //通知curses函数库,ptr指定窗口发生变化,下次wrefresh时,需重绘窗口。利用该函数,安排要显示的窗口
  int scrollok(WINDOW *ptr, bool sf);    //true,允许窗口卷屏。默认不允许
  int scroll(WINDOW *ptr);        //调用scrollok后,再调用scroll,把窗口内容上卷一行。

2.再看一眼——即将绝迹的远古时代的 BBS

在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"。
在这里插入图片描述
打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net,以游客身份体验一下即将绝迹的远古时代的 BBS
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.安装curses库

>>利用此命令安装curse
sudo apt-get install libncurses5-dev

在这里插入图片描述

curses函数库的头文件安装在/usr/include/下,库文件安装在/usr/lib/下。

4.休闲一下——Linux下的贪吃蛇小游戏

Linux贪吃蛇中下载代码,在Linux下建立文件,输入程序,保存为tanchishe.c。编译时,使用

cc tanchishe.c -lcurses -o tanchishe

进行编译,随后运行,就可以开心的做一条贪吃蛇了。
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值