LInux中gcc编译工具

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

1.第1步:编辑生成例子程序hello.h、hello.c和main.c

(1)可以在Linux中创建一个文件夹来保存本次练习。在终端的代码如下:

#mkdir test1
#cd test1

第一个命令是创建名为test1的文件夹
第二个命令是进入test1这个文件夹

(2)接下来用vim编辑器编写下列三个程序:
程序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.第2步:将hello.c编译成.o文件

我们在终端输入如下命令:

gcc -c hello.c

然后我们输入ls命令查看是否得到了hello.o文件:在这里插入图片描述

3.第3步:由.o文件创建静态库文件

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

ar -crv libmyhello.a hello.o

我们同样运行 ls 命令查看结果:
在这里插入图片描述

4.第4步:在程序中使用静态库

静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包 含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从 静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追 加扩展名.a 得到的静态库文件名来查找静态库文件。 在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用 公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果如何。
(1)方法一:
输入如下代码:

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

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

gcc main.c libmyhello.a -o hello

小编用的就是方法二,得到了可执行文件hello,如图:
在这里插入图片描述
(3)方法三:

先生成 main.o:

 gcc -c main.c 

再生成可执行文件:

gcc -o hello main.o libmyhello.a 

动态库连接时也可以这样做。
(4)运行可执行程序hello,命令如下:

./hello

查看结果:
在这里插入图片描述

5.第5步:由.o文件创建动态库文件

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

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

-o不可少

然后我们用ls命令来查看动态文件是否生成:
在这里插入图片描述

6.第6步:在程序中使用动态库

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

gcc main.c libmyhello.so -o hallo

我们依然用ls命令来查看是否生成了hallo文件:
在这里插入图片描述
这里我们用./hallo命令运行hallo,却出现了如下状况:在这里插入图片描述

原因是因为找不到动态库文件 libmyhello.so。程序在运行时, 会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提 示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,再试试。

输入如下命令:
在这里插入图片描述

这里使我们的权限不够,那我们在前面加入sudo试试。

在这里插入图片描述
输入密码后成功。我们再用./hallo运行一下hallo文件:
在这里插入图片描述

补充

补充 1:编译参数解析 最主要的是 GCC 命令行的一个选项: -shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成 弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。 -fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载 入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。 -L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在 源文件后面就可以了。比如: gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop 其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如 前面的“法二 #gcc main.c libmyhello.a -o hello”) -lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面 加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。 LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。 当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到 同样的目的,不过如果没有 root 权限,那么只能采用输出 LD_LIBRARY_PATH 的方法了。 调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看 时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改 LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决 库无法链接的问题了。
补充 2: 从上述可知,如何找到生成的动态库有 3 种方式: (1)把库拷贝到/usr/lib 和/lib 目录下。 (2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。 例如动态库 libhello.so 在/home/example/lib 目录下: e x p o r t L D L I B R A R Y P A T H = export LD_LIBRARY_PATH= exportLDLIBRARYPATH=LD_LIBRARY_PATH:/home/example/lib (3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。这样, 加入的目录下的所有库文件都可见。 附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到: g++ -o main main.cpp -L/usr/lib libpthread.a必须这样 g++ -o main main.cpp -L/usr/lib -lpthread 才正确 。 自定义的库考到/usr/lib 下时, g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a 会出错,但是这样 g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass 就正确了。

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

1.创建一个文件保存本次练习

mkdir test2 
cd test2

2.用vim编辑如下四个文件A1.c、A2.c、A.h、test.c:

(1)A1.c:

#include <stdio.h>
 void print1(int arg)
 { 
 printf("A1 print arg:%d\n",arg);
  }

(2)A2.c:

#include <stdio.h> 
void print2(char *arg)
{
 printf("A2 printf arg:%s\n", arg); 
 }

(3)A.h

#ifndef A_H 
#define A_H 
void print1(int); 
void print2(char *); 
#endif

(4)test.c:

#include <stdlib.h>
 #include "A.h" 
 int main()
 { 
 print1(1); 
 print2("test"); 
 exit(0); 
 }

在这里插入图片描述

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

(1)生成目标文件
使用命令如下:

gcc -c A1.c A2.c

得到如下结果:
在这里插入图片描述
(2)生成静态库.a文件
使用命令如下:

ar crv libafile.a A1.o A2.o

得到如下结果:
在这里插入图片描述

在这里插入图片描述
(3)使用.a 库文件,创建可执行程序
(若采用此种方式,需保证生成的.a 文件与.c 文件保 存在同一目录下,即都在当前目录下)
输入如下命令:

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

得到如下结果:

在这里插入图片描述

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

(4)我们运行一下,却出现了如下的问题:
在这里插入图片描述

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

(5)我们将对应的.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)

三、 gcc的动态库与静态库使用的实例

本次实例是在小编之前写的博客的改变,如果有需要或者看不懂可以去看看小编之前的博客,链接如下。

链接: Ubuntu下gcc和Makefile编译.

1.新增x2y函数,功能为将两个数相加并返回值
用vim编辑器编写如下:
在这里插入图片描述
2.修改主函数
在这里插入图片描述

3.将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件

在这里插入图片描述
在这里插入图片描述
4.将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件
在这里插入图片描述
5.然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序在这里插入图片描述

在这里插入图片描述
6.此时的可执行文件大小
在这里插入图片描述

7.将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件

在这里插入图片描述
在这里插入图片描述
8.然后用 gcc将 main函数的目标文件与此动态库文件进行链接

在这里插入图片描述
9.此时文件的大小
在这里插入图片描述
我们通过对比发现,两种方式生成的可执行文件的大小一样。

四、 Linux GCC常用的命令

1.一步到位的编译指令

gcc test.c -o test

示例程序如下:在这里插入图片描述
输入上面的命令,我们运行试一下:

在这里插入图片描述

实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)

2.预处理

命令如下

gcc -E test.c -o test.i 或 gcc -E test.c

在这里插入图片描述
我们可以用cat命令查看可执行文件,这里代码过长,小编就不在展示了。

3.编译为汇编代码

命令如下:

gcc -S test.i -o test.s

在这里插入图片描述

4.汇编

命令如下:

gcc -c test.s -o test.o

在这里插入图片描述

5.连接

命令如下:

gcc test.o -o test

在这里插入图片描述

五、GCC编译器背后的故事

1.准备工作

由于 GCC 工具链主要是在 Linux 环境中进行使用,因此本文也将以 Linux 系统作 为工作环 境。为了能够 演示编译的整个 过程,先创建一 个工作目录 test0,然后 用文本编辑器生成一个 C 语言编写的简单 Hello.c 程序为示例,其源代码如下所 示:

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

2.编译过程

2…1预处理

预处理的过程主要包括以下过程:

(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编 译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
使用 gcc 进行预处理的命令如下:
在这里插入图片描述

将源文件 hello.c 文件预处理生成 hello.i ;GCC 的选项-E 使 GCC 在进行完预处理后即停止

2.2编译

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及 优化后生成相应的汇编代码。
使用 gcc 进行编译的命令如下:
在这里插入图片描述

将预处理生成的 hello.i 文件编译生成汇编程序 hello.s ;GCC 的选项-S 使 GCC 在执行完编译后停止,生成汇编程序

2.3汇编

汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o 的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相 对于编译过程比较简单,通过调用 Binutils 中的汇编器 as 根据汇编指令和处理 器指令的对照表一一翻译即可。 当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标 文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部 分了,但是在链接之前还不能执行。
使用 gcc 进行汇编的命令如下:
在这里插入图片描述

将编译生成的 hello.s 文件汇编生成目标文件 hello.o ;GCC 的选项-c 使 GCC 在执行完汇编后停止,生成目标文件 ;或者直接调用 as 进行汇编 命令入下:$ as -c hello.s -o hello.o ;使用 Binutils 中的 as 将 hello.s 文件汇编生成目标文件 注意:hello.o 目标文件为 ELF(Executable and Linkable Format)格式的可 重定向文件

2.4链接

链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行 文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链 接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完 成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和 重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统 中把相应动态库加载到内存中去。 
在 Linux 系 统中,gcc 编 译链 接时 的动 态库 搜索 路径 的 顺序 通常 为:首 先从 gcc 命 令的 参 数-L 指 定的 路径 寻找 ;再 从环 境变 量 LIBRARY_PATH 指 定的 路径 寻址;再 从默 认路 径 /lib、/usr/lib、 /usr/local/lib 寻找 。 
在 Linux 系 统中,执 行二 进制 文件 时的 动态 库搜 索路 径的 顺序 通常 为:首 先搜 索编 译目 标 代码 时指 定的 动态 库搜 索路 径;再 从环 境变 量 LD_LIBRARY_PATH 指 定的 路径 寻址;再 从 配置 文件/etc/ld.so.conf 中 指定 的动 态库 搜索 路径 ;再 从默 认路 径/lib、/usr/lib 寻找 。 
在 Linux 系统 中, 可以 用 ldd 命令 查看 一个 可执 行程 序依 赖的 共享 库。
由于链接动态库和静态库的路径可能有重合,所以如果在路径中有同名的静态库文件和动 态库文件,比如 libtest.a 和 libtest.so,gcc 链接时默认优先选择动态库,会链接 libtest.so,如果要让 gcc 选择链接 libtest.a 则可以指定 gcc 选项-static,该选项会强 制使用静态库进行链接。以 Hello World 为例: 如果使用命令“gcc hello.c -o hello”则会使用动态库进行链接,生成的 ELF 可执行文件的大小(使用 Binutils 的 size 命令查看)和链接的动态库 (使用 Binutils 的 ldd 命令查看)如下所示
在这里插入图片描述

如 果 使 用 命 令 “ gcc -static hello.c -o hello”则 会 使 用 静 态 库 进 行 链 接 , 生成的 ELF 可执行文件的大小(使用 Binutils 的 size 命令查看)和链接的 动态库(使用 Binutils 的 ldd 命令查看)如下所示:
在这里插入图片描述

链接器链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常 被链接为不同的段,常见的段譬如.text、.data、.rodata、.bss 等段。

3.分析 ELF 文件

3.1ELF 文件的段

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

可以使用 readelf -S 查看其各个 section 的信息如下:在这里插入图片描述

3.2反汇编 ELF

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

使用 objdump -D 对其进行反汇编如下:在这里插入图片描述
在这里插入图片描述

六、nasm编译实例

as汇编编译器针对的是AT&T汇编代码风格,Intel风格的汇编代码则可以用nasm汇编编译器编译生成执行程序,在Ubuntu在下载nasm。命令入下:

sudo apt install nasm

用gedit编辑一个hello.asm文件,代码如下:
在这里插入图片描述
然后用nasm编辑.asm文件,命令入下:

在这里插入图片描述
最后我们用ld链接器把hello.o生成可执行文件hello:
在这里插入图片描述
然后我们运行一下:在这里插入图片描述
结果正确,接着我们来看看一下此时可执行文件的大小:
在这里插入图片描述
与之前gcc直接编译的可执行文件的大小相比较:
在这里插入图片描述
可以看出用nasm编辑出的可执行文件大小远远小于用gcc编译出可执行文件的大小。

七、Linux 系统中终端程序最常用的光标库(curses)的主要函数功能

1.从屏幕读取

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

2.清除屏幕

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

3.窗口移动和更新屏幕

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);   //把窗口内容上卷一行

八、curses的头文件的安装目录

首先我们用下面命令安装:

sudo apt-get install libncurses5-dev

然后我们用 whereis 命令查看curses.h安装在哪里:
在这里插入图片描述

九、游客身份体验一下即将绝迹的远古时代的 BBS

1.在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”

在这里插入图片描述
2.启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)
在这里插入图片描述
在这里插入图片描述
3.然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net,即可体验

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

参考网站链接: Linux 环境下C语言编译实现贪吃蛇游戏.
将上面的代码复制到linux的文件中,并在终端输入
gcc mysnake1.0.c -lcurses -o mysnake1.0,最后运行可执行文件,就可以体验一下贪吃蛇游戏了。

在这里插入图片描述

十一、总结

通过本次实验,小编我学习到了如何用gcc生成静态库和动态库、静态库.a与.so库文件的生成与使用、
gcc编译工具集中各软件的用途、EFF文件格式、
Linux GCC常用命令、nasm编辑器的使用方法等等
强烈推荐大家也跟着做一遍,相信你们也会有不小的收获!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值