编译器编译的过程

本文详细介绍了如何在Linux中创建和使用静态库与动态库,包括使用gcc编译.o文件,创建静态库(.a)和动态库(.so),以及在程序中链接和调用这些库。实验过程包括从创建源文件到生成可执行程序的完整步骤,并通过实例演示了静态库和动态库的生成与应用,强调了静态库在程序编译时会被连接,而动态库在运行时加载。最后,文章讨论了Linux GCC常用命令、as汇编编译器和第三方库函数,如curses库的应用。

一.用gcc生成静态库和动态库

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
本文主要通过举例来说明在Linux 中如何创建静态库和动态库,以及使用它们。

1.生成例子程序hello.h,hello.c,main.c

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

在这里插入图片描述
用vim创建和编辑
在这里插入图片描述
hello.c是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出"Hello XXX!"。hello.h为该函数库的头文件。main.c为测试库文件的主程序,在主程序中调用了公用函数hello。

代码如下

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 通过gcc 先编译成.o 文件。在系统提示符下键入以下命令得到hello.o 文件。

$ gcc -c hello.c

用ls查看当前目录下发现以及生成hello.o文件
在这里插入图片描述

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

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

创建静态库用ar 命令。在系统提示符下键入以下命令将创建静态库文件。

$ ar -crv libmyhello.a hello.o

该命令的意思是创建静态库文件libmyhello.a。
在这里插入图片描述

4.在程序中使用静态库

在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。

attention:gcc会在静态库名前加上前缀lib,然后追加拓展名.a得到的静态库文件名来查找静态库文件

因为在main.c中,包含了静态库的头文件hello.h,然后在主程序main中就可以直接调用公用函数hello,

方法一:
输入以下代码

$ gcc -o hello main.c -L. –lmyhello

自定义的库时,main.c 还可放在-L.和–lmyhello 之间,但是不能放在它俩之后,否则会提示myhello 没定义, 但是是系统的库时,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出错。

然后运行Hello文件,文件被顺利执行

在这里插入图片描述
方法二:

输入以下代码

$ gcc main.c libmyhello.a -o hello

在这里插入图片描述运行之后,也成功得到了正确的结果

方法三:

首先生成main.o:

$ gcc -c main.c

再生成可执行文件

$ gcc -o hello main.o libmyhello.a

接着运行程序
在这里插入图片描述
也成功得到了结果!

动态链接时也可以这样做
我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。

$ rm libmyhello.a
$ ./hello

在这里插入图片描述如图所示,删除掉静态库文件后也能连接到目标文件hello中,静态库中的公用函数已经连接到目标文件中了。

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

动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc 来创建动态库。在系统提示符下键入以下命令得到动态库文件libmyhello.so。

$ gcc -shared -fPIC -o libmyhello.so hello.o

其中 -o 不可少
使用ls命令发现动态文件库已经生成
在这里插入图片描述

6.在程序中使用动态库

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

$ gcc -o hello main.c -L. -lmyhello
$ ./hello

而现在控制台却会报如下的错误

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

这是因为我们少做了一个步骤,程序无法找到动态文件库。我们需要把文件 libmyhello.so 复制到目录/usr/lib中程序才能运行
输入如下命令

$ sudo mv libmyhello.so /usr/lib

在这里插入图片描述至此,再次输入./hello就能成功运行了
在这里插入图片描述

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

1.创建目录和任务所需的相应的文件

和上一个例子的方法一样,新建test2文件夹后用vim创建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("A1 print arg:%s\n",arg);
}

A.h

#ifdef 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);
}

2.静态库.a 文件的生成与使用

首先,我们需要生成目标文件.o文件

$ gcc -c A1.c A2.c

在这里插入图片描述
接下来再生成静态库.a文件
在这里插入图片描述使用.a 库文件,创建可执行程序

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

在这里插入图片描述

3.共享库.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

在这里插入图片描述
直接这样使用出现了错误,我们可以运行ldd test,来查看链接情况
在这里插入图片描述
发现图中并没有对应的.so文件。

查阅资料后发现,这是由于Linux自身系统设定的相应的设置的原因,即只在/lib 和 /usr/lib下搜索对应的.so文件,所以我们需要用cp命令复制.so文件到指定的文件夹

$ sudo cp libsofile .so /usr/lib

在这里插入图片描述这样就能成功运行,解决掉了这个问题了!

还有一种方法
直接使用

$ gcc -o test test.c -L. -lname

来使用相应的库文件

其中,

-L.:表示在当前目录下,可自行定义路径path,即使用-Lpath 即可。

-lname:name:即对应库文件的名字(除开lib),即若使用libafile.a,则name 为afile;若要使用libsofile.so,则name 为sofile。

4.练习

a.静态库的练习并记录大小

题目:沿用上次的一个x2x函数(输出两个数的和),新建一个x2y函数(功能自定),main函数代码将调用x2x和x2y;
将这3个函数分别写成单独的3个 .c文件。
(1)创建一个头文件head.h
代码如下:

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

(2)创建一个sub2.c文件包含x2y函数,作用是输出两个数的差值
代码如下

#include<stdio.h>
float x2y(int a,int b)
 {
         float c=0;
         c=a/b;
         return c;
 }

(3)创建main.c文件

#include<stdio.h>
#include"head.h"
void main()
{
        int a=2,b=1;
        printf("%d\n",x2x(a,b));
        printf("%d\n",x2y(a,b));
}

(4)用gcc分别编译为.o文件
在这里插入图片描述(5)将sub1、sub2用ar工具生成一个.a的静态库文件
在这里插入图片描述(6)用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序并记录文件的大小

在这里插入图片描述
(7)查看静态库生成的文件大小
在这里插入图片描述

b.动态库练习并记录大小

(1)生成动态库

在这里插入图片描述
(2)使用.so库文件,创建可执行程序
在这里插入图片描述(3)使用同样的方法查看动态库生成文件的大小
在这里插入图片描述
通过比较发现静态库要比动态库要小很多,生成的可执行文件大小也存在较小的差别。

三.Linux GCC常用命令

GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理C语言。GCC 很快地扩展,变得可处理 C++。后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective-C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)

gcc命令下各选项的含义

-E:仅作预处理,不进行编译、汇编和链接
-S:仅编译到汇编语言,不进行汇编和链接
-c:编译、汇编到目标代码(也就是计算机可识别的二进制)
-o:执行命令后文件的命名
-g:生成调试信息
-w:不生成任何警告
-Wall:生成所有的警告

gcc编译的四个步骤

预处理:gcc -E Test.c -o Test.i
编译: gcc -S Test.i -o Test.s
汇编: gcc -c Test.s -o Test.o
链接生成可执行文件: gcc Test.o -o Test

gcc常用的编译代码

这里只介绍部分
1、ar
用于创建静态链接库。为了便于初学者理解,在此介绍动态库与静态库 的概念:
(1)如果要将多个.o目标文件生成一个库文件,则存在两种类型的库,一种是静态库,另一种是动态库。
(2)在windows中静态库是以.lib为后缀的文件,共享库是以.dll为后缀的文件。在linux中静态库是以.a为后缀的文件,共享库是以.so为后缀的文件。
(3)静态库和动态库的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。
(4)如果一个系统中存在多个需要同时运行的程序且这些程序之间存在共享库,那么采用动态库的形式将更节省内存。

2、ld
用于链接。

3、as
用于汇编。

4、ldd
可以用于查看一个可执行程序依赖的共享库。

5、size
查看执行文件中各部分的大小。

6、addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。

四.as汇编编译器

1.as汇编编译器简介

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

2.在ubuntu中下载安装nasm编译器

在控制台输入

$ sudo apt install nasm

在这里插入图片描述(下载之前记得更换下载源,不然速度会很慢)

3.将代码编译生成可执行程序

先用vim创建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         ; 调用内核功能

使用如下命令:

生成hello.o文件

$ nasm -f elf64 hello.asm

生成可执行文件hello

$ ld -s -o hello.o

执行./hello输出
在这里插入图片描述图为nasm输出hello world生成程序的大小与gcc编译的大小的对比
在这里插入图片描述对比可知nasm方式编译得到的可执行文件要比用gcc编译得到的文件要小很多。

五.Linux中的第三方库函数

1.Linux 系统中终端程序最常用的光标库—— curses

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

curses函数库能够优化光标的移动并最小化需要对屏幕进行的刷新,从而也减少了必须向字符终端发送的字符数目。

(2)一些基本函数名称及功能

从屏幕读取:

chtype inch(void);  //返回光标位置字符
int instr(char *string);  //读取字符到string所指向的字符串中
int innstr(char *string, int numbers);//读取numbers个字符到string所指向的字符串中

清除屏幕:

int erase(void);//在屏幕的每个位置写上空白字符
int clear(void);//使用一个终端命令来清除整个屏幕,相当于vi内的Ctrl+L
//内部调用了clearok来执行清屏操作,(在下次调用refresh时可以重现屏幕原文)

int clrtobot(void);//清除光标位置到屏幕结尾的内容
int clrtoeol(void);//清除光标位置到该行行尾的内容

移动光标:

int move(int new_y, int new_x);    //移动stdcsr的光标位置
int leaveok(WINDOW *window_ptr,bool leave_flag);
//设置一个标志,用于控制在屏幕刷新后curses将物理光标放置的位置。

窗口移动和更新屏幕:

int mvwin(WINDOW *win, int new_y, int new_x);   //移动窗口
int wrefresh(WINDOW *win);
int wclear(WINDOW *win);
int werase(WINDOW *win);
//类似于上面的refresh, clear, erase,但是此时针对特定窗口操作,而不是stdcur

int touchwin(WINDOW *win);     //指定该窗口内容已改变、
//下次wrefresh时,需重绘窗口。利用该函数,安排要显示的窗口
	
int scrollok(WINDOW *win, bool flag);    //指定是否允许窗口卷屏
int scroll(WINDOW *win);   //把窗口内容上卷一行

窗口优化屏幕刷新:

int wnoutrefresh(WINDOW *window_ptr);
//The wnoutrefresh subroutine determines which parts of the terminal may need updating.
	
int doupdate(void);
//The doupdate subroutine sends to the terminal the commands to perform any required changes.

2.时代遗迹——BBS

BBS是一个用键盘光标控制的终端程序,为什么说他是时代遗迹呢,下图可见
在这里插入图片描述百度都已经这样说了

现在国内还有唯一一个有人维护的BBS,let’s check it out

(1)在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net。
在这里插入图片描述
在这里插入图片描述在命令行输入telnet bbs.newsmth.net即可体验
在这里插入图片描述

3.curses库的安装

在Ubuntu中用 sudo apt-get install libncurses5-dev 安装curses库
在这里插入图片描述查找curses.h等文件可用whereis + 文件名 的方法查找
在这里插入图片描述

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

代码参考网站:

链接: http://www.linuxidc.com/Linux/2011-08/41375.htm.
用vim建立.c文件snake.c,复制代码到unbuntu
再运行命令:

$ gcc snake.c  -lcurses -o snake
$ ./snake

在这里插入图片描述这样就可以成功运行贪吃蛇游戏了!

六.总结

通过本次实验,了解到了c语言不同的编译方式带来的不同的结果,以及一些实用的第三方库,在编写中遇到困难解决问题也提升了自身的能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值