简单探寻GCC编译器背后的故事

这篇博客介绍了如何使用GCC在Linux环境下创建和使用静态库(.a)和动态库(.so)。通过实例演示了从源代码到.o文件,再到静态库和动态库的步骤,并展示了如何在程序中链接这些库。同时,讨论了静态库和动态库的区别,以及它们在程序执行时的不同行为。此外,还简要探讨了GCC的编译过程,包括预处理、编译、汇编和链接,并提到了光标库(curses)的功能和Linux下C语言实现贪吃蛇游戏的示例。

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

一、用gcc生成 .a静态库和 .so动态库

所谓静态库,就是在静态编译时由编译器到指定目录寻找并且进行链接,一旦链接完成,最终的可执行程序中就包含了该库文件中的所有有用信息,包括代码段、数据段等,程序运行时将不再需要该静态库。
所谓动态库,就是在应用程序运行时,由操作系统根据应用程序的请求,动态到指定目录下寻找并装载入内存中,同时需要进行地址重定向。库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,所以在程序运行时还需 要动态库存在。

1、编辑生成例子程序

创建一个 test1 文件夹,并在该文件夹中创建三个子程序 hello.h、hello.c 和 main.c

wk@ubuntu:~$ mkdir test1  //创建test1文件夹
wk@ubuntu:~$ ls            //查看一下自己的文件是否建立存在
Desktop  Documents  Downloads  examples.desktop  h  hello,c  hello.c  hello.h  hello.o  main  makefile  Music  Pictures  Public  sub1.c  sub1.o  Templates  test1  Videos 
wk@ubuntu:~$ cd test1           //进入test1文件
wk@ubuntu:~/test1$ vim hello.h  //编辑hello.h
wk@ubuntu:~/test1$ vim hello.c  // 编辑hello.c
wk@ubuntu:~/test1$ vim main.c   // 编辑main.c
wk@ubuntu:~/test1$ ls           //在test1文件夹中查看刚刚创建的三个子程序
hello.c  hello.h  main.c

写入子程序hello.h、hello.c、main.c的源代码分别如下
hello.h

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

hello.c

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

main.c

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

2、将hello.c编译成 .o文件

无论静态库,还是动态库,都是由.o 文件创建的。因此,我们必须将源程序 hello.c 通过 g cc 先编译成.o 文件。

wk@ubuntu:~/test1$ gcc -c hello.c   //编译完成 .o文件
wk@ubuntu:~/test1$ ls      //查看

hello.o就是编译后生成的文件
在这里插入图片描述

3、由 .o文件创建静态库

静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为 .a。 例如:我们将 创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。

创建静态库文件 libmyhello.a

ar -crv libmyhello.a hello.o  //生成静态库
ls    //查看

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

4、在程序中使用静态库

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包 含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从 静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追 加扩展名.a 得到的静态库文件名来查找静态库文件。

在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用 公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。

建立可执行程序三种方法:

# 方法一
gcc -o hello main.c -L. -lmyhello
     //自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提 示 myhello 没定义,但是是系统的库时,如 g++ -o main(-L/usr/lib) -lpthread main.cpp 就不出错。
# 方法二
gcc main.c libmyhello.a -o hello
# 方法二
gcc -o main.c	// 先生成 main.o
gcc -o hello main.o libmyhello.a  //再生成可执行文件

然后 ./hello 运行可执行程序
在这里插入图片描述
我们可尝试删除 libmyhello静态库,再次执行 hello 程序(看看公用函数hello是否真的连接到了目标文件hello中了)

rm libmyhello.a	     //删除libmyhello.a
./hello	       // 运行hello程序

在这里插入图片描述
这里发现程序照常运行,说明静态库中的公用函数已经连接到目标文件中,即程序运行时将不再需要该静态库了。

5、由.o 文件创建动态库文件

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其 文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh ello.so。

gcc -shared -fPIC -o libmyhello.so hello.o	 // 生成动态库

在这里插入图片描述

-shared:该选项指定生成动态连接库
-fPIC:表示编译为位置独立的代码

6、在程序中使用动态库

在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含 这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我 们先运行 gcc 命令生成目标文件,再运行它看看结果。

两种方法生成目标文件:
gcc -o hello main.c -L. -lmyhello  //方法一
gcc main.c libmyhello.so -o hello  //方法二
./hello                            //运行

啊!在运行hello时就出现报错了
在这里插入图片描述

这是因为虽然连接时用的是当前目录的动态库,但是程序在运行时,会在/usr/lib 和/lib等目录中查找需要的动态库文件;若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。

怎么办呢?
既然在/usr/lib 和/lib等目录中找不到,那我们将文件 libmyhello.so 复制到目录/usr/lib 中试试
在这里插入图片描述
哦,这样就成功编译了。
前面这些内容可以参考这个用gcc生成静态库和动态库

二、静态库和动态库实践

1、重新创建test2文件夹,然后写入sub1.h、sub1.c、sub2.h、sub2.c、main.c等子程序

命令和代码如下:

mkdir test2
cd test2
vim sub1.h
vim sub1.c
vim sub2.h
vim sub2.c
vim main.c

sub1.h

#ifndef SUB1-H
#define SUB1_H
float x2x(int a, int b);
#endif

sub1.c

#include"sub1.h"
float x2x(int a,int b)
{
        return a+b;
}

sub2.h

#ifndef SUB2_H
#define SUB2_H
float x2y(int c, int d);
#endif

sub2.c

#include"sub2.h"
float x2y(int c, int d)
{       
        return c*d;
}    

main.c

#include<stdio.h>
#include"sub1.h"
#include"sub2.h"
int main()
{
        int a;
        int b;
        int c;
        int d;
        printf("input a=");
        scanf("%d",&a);
        printf("input b=");
        scanf("%d",&b);
        printf("input c=");
        scanf("%d",&c);
        printf("input d=");
        scanf("%d",&d);
        printf("%d + %d =%f",a,b,x2x(a,b));
        printf("%d * %d =%f",c,d,x2y(c,d));
        return 0;
}

在这里插入图片描述

2、用静态库文件进行链接,生成可执行文件

(1)将 sub1.c、sub2.c 编译成 .o文件

gcc -c sub1.c sub2.c

在这里插入图片描述
(2) .o文件创建静态库

ar -crv libsub1.a sub1.o
ar -crv libsub2.a sub2.o

在这里插入图片描述
(3)在程序中使用静态库

gcc main.c libsub1.a libsub2.a -o main
./main

有点小瑕疵,不影响(后面改掉)

在这里插入图片描述

3、用动态库文件进行链接,生成可执行文件

(1).o文件创建动态库

gcc -shared -fPIC -o libsub1.so sub1.o
gcc -shared -fPIC -o libsub2.so sub2.o

在这里插入图片描述
(2)在程序中使用动态库

gcc main.c libsub1.so libsub2.so -o main2
sudo mv libsub1.so /usr/lib    //
sudo mv libsub2.so /usr/lib    //这两步将文件 libsub1.so、libsub2.so 移动到目录/usr/lib 中;前面讲了其原因
./main2

在这里插入图片描述

4、查看比较两个可执行文件大小

size main1
size main2

在这里插入图片描述
按正常的静态动态文件大小对比,静态的要比动态的大很多。(不好意思,我这里出了点问题,等我弄清楚了,来解释修改)

三、GCC不是一个人在战斗,GCC的编译过程

由于 GCC 工具链主要是在 Linux 环境中进行使用,因此本文也将以 Linux 系统作 为工作环境。

1、编译过程

1、为了能够演示编译的过程,先创建一 个工作目录 test0,然后用文本编辑器生成一个 C 语言编写的简单 Hello.c 程序为示例,其源代码如下:

mkdir test0
cd test0
vim hello.c

在这里插入图片描述
hello.c 内容如下:

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

2、程序的编译过程
在这里插入图片描述

gcc -E hello.c -o hello.i  //将源文件 hello.c 文件预处理生成 hello.i

gcc -S hello.i -o hello.s  //将预处理生成的 hello.i 文件编译生成汇编程序 hello.s

gcc -c hello.s -o hello.o  //将编译生成的 hello.s 文件汇编生成目标文件 hello.o
//用gcc进行汇编
as -c hello.s -o hello.o
//用as进行汇编
gcc hello.c -o hello  //分为静态链接和动态链接,生成可执行文件
//动态链接
gcc -static hello.c -o hello
//静态链接

在这里插入图片描述
3、用 size 查看文件大小,ldd链接了那些动态库
在这里插入图片描述

2、FLE文件分析

链接器链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常 被链接为不同的段。

1、一个典型的 ELF 文件包含下面几个段
(1) .text:已编译程序的指令代码段

(2) .rodata:ro 代表 read only,即只读数据(譬如常数 const)

(3) .data:已初始化的 C 程序全局变量和静态局部变量

(4) .bss:未初始化的 C 程序全局变量和静态局部变量

(5) .debug:调试符号表,调试器用此段的信息帮助调试
在这里插入图片描述
可以使用 readelf -S 查看其各个 section 的信息

readelf -S hello  //查看各个section(段)的信息

在这里插入图片描述
2、反汇编

由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包 含的指令和数据,需要使用反汇编的方法。

objdump -S 将其反汇编并且将其 C 语言源代码混合显示出来:

gcc -o hello -g hello.c
objdump -S hello

在这里插入图片描述

或者直接使用 objdump -D hello进行反汇编(不会有C语言源码)

objdump -D hello

在这里插入图片描述

3、用nasm汇编编译器编译生成执行程序

1、在ubuntu中下载安装nasm编译器
呃呃呃呃呃,虚拟机有丢丢问题,后面解决了子再来完善这部分

四、简单了解实际程序是如何借助第三方库函数完成代码设计

1、光标库(curses)的主要函数功能

initscr(): initscr() 是一般 curses 程式必须先呼叫的函数, 一但这个函数被呼叫之後, 系统将根据终端机的形态并启动 curses 模式.

endwin(): curses 通常以呼叫 endwin() 来结束程式. endwin() 可用来关闭curses 模式, 或是暂时的跳离 curses 模式. 如果您在程式中须要call shell ( 如呼叫 system() 函式 ) 或是需要做 system call, 就必须先以 endwin() 暂时跳离 curses 模式. 最後再以wrefresh() doupdate() 来重返 curses 模式.

cbreak() and nocbreak(): 当 cbreak 模式被开启後, 除了 DELETE 或 CTRL 等仍被视为特殊控制字元外一切输入的字元将立刻被一一读取.当处於 nocbreak 模式时, 从键盘输入的字元将被储存在 buffer 里直到输入 RETURN或 NEWLINE.在较旧版的 curses 须呼叫crmode(),nocrmode() 来取代 cbreak(),nocbreak()

nl() and nonl(): 用来决定当输入资料时, 按下 RETURN 键是否被对应为 NEWLINE 字元 ( 如 /n ). 而输出资料时, NEWLINE 字元是否被对应为 RETURN 和 LINDFEED系统预设是开启的.

echo() and noecho(): 此函式用来控制从键盘输入字元时是否将字元显示在终端机上.系统预设是开启的.

intrflush(win,bf): 呼叫 intrflush 时须传入两个值, win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr. bf 为 TRUE 或 FALSE. 当 bf 为 true 时, 当输入中断字元 ( 如 break) 时, 中断的反应将较为快速.但可能会造成萤幕的错乱.

keypad(win,bf): 呼叫 keypad 时须传入两个值, win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr. bf 为 TRUE 或 FALSE. 当开启 keypad 後, 可以使用键盘上的一些特殊字元, 如上下左右>等方向键, curses 会将这些特殊字元转换成 curses.h 内定义的一些特殊键. 这些定义的特殊键通常以 KEY_ 开头.

refresh(): refresh() 为 curses 最常呼叫的一个函式. curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改变萤幕上的画面时, curses 并不会立刻对萤幕做改变, 而是等到refresh() 呼叫後, 才将刚才所做的变动一次完成. 其馀的资料将维持不变. 以尽可能送最少的字元至萤幕上. 减少萤幕重绘的时间.如果是 initscr() 後第一次呼叫 refresh(), curses 将做清除萤幕的工作.

更多库函数的详细功能请参考:Linux curses库

2、以游客身份体验一下即将绝迹的远古时代的 BBS(一个用键盘光标控制的终端程序)

1、设置:
在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”;启用 “telnet client” 和"适用于Linux的Windows子系统"
在这里插入图片描述
2、以游客身份体验远古时代的BBS
打开一个 cmd命令行窗口,输入如下命令:

 telnet bbs.newsmth.net

在这里插入图片描述
进入后的界面
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、Linux 环境下C语言编译实现贪吃蛇游戏

1、安装curses库

sudo apt-get install libncurses5-dev

在这里插入图片描述
然后可以通过whereis 命令头文件和库文件查看被安装到哪些目录中:
在这里插入图片描述
2、Linux 环境下C语言编译实现贪吃蛇游戏

mkdir t_snake    //新建一个文件夹
cd t_snake       //进入文件
vim mysnake.c    //进入编辑,代码见后面链接
gcc mysnake.c -lcurses -o mysnake    //编译链接生成可执行文件
./mysnacke       //执行程序

mysnake.c 的内容请参考:Linux 环境下C语言编译实现贪吃蛇游戏

在这里插入图片描述
程序运行效果如下:
在这里插入图片描述

参考链接

GCC—探寻编译器背后的故事
Linux curses库
Linux 环境下C语言编译实现贪吃蛇游戏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值