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的动态库与静态库使用的实例
本次实例是在小编之前写的博客的改变,如果有需要或者看不懂可以去看看小编之前的博客,链接如下。
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编辑器的使用方法等等
强烈推荐大家也跟着做一遍,相信你们也会有不小的收获!