GCC命令背后故事以及静、动态库应用
文章目录
一.用gcc生成静态库和动态库
静态库与动态库 .我们通常把一些公用函数制作成函数库 ,供其它程序使用。函数库分为静态库和动态库两种。 静态库在程序编译时 会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程 序编译时并不会被连接到目标代码中,而是在程序运行 时才被载入,因此在程序运行时还需 要动态库存在。 本文主要通过举例来说明在 Linux 中如何创建静态库和动态库,以及使用它们。
1. 建立子程序 hello.h、hello.c 和main.c。
首先建立一个工作目录test1,并在在此目录下建立需要的子程序文件,如下:
#mkdir test1
#cd test1
然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件。
hello.c(见程序 2)是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出"Hello XXX!"。hello.h(见程序 1)为该函数库的头文件。main.c(见程序 3)为测试库文件的主程序, 在主程序中调用了公用函数 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 文件。
gcc -c hello.c
然后ls命令可以看到生成了hello.o
3. 由.o 文件创建静态库。
静态库文件名的命名规范是以lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时, 需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件 libmyhello.a。
ar -crv libmyhello.a hello.o
可以看到结果如图:(已生成静态库文件 libmyhello.a
)
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
方法三:
先生成main.o:
#gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a
动态库连接时也可以这样做。(运行如图:)
删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了
从结果看出静态库中的公用函数已经连接到目标文件中了。
5. 由.o 文件创建动态库文件。
gcc -shared -fPIC -o libmyhello.so hello.o
开始报错,我的解决办法:
此时可以正常运行,使用ls 命令查看动态库文件已经生成
libmyhello.so
6. 在程序中使用动态库
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含 这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我 们先运行 gcc 命令生成目标文件,再运行它
gcc -o hello main.c -L. -lmyhello
我们会发现在运行hello的时候会产生报错。这是因为虽然连接时用的是当前目录的动态库,但是运行时,是到 /usr/lib 中找库文件的,需要将文件 libmyhello.so 复制到目录/usr/lib 。
我们可以看到此时成功,这说明动态库说明在程序运行之时被载入。
我们回过头看看,发现使用静态库和使用动态库编译成目标程序使用的 gcc 命令完全一样, 那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?抱着对问题必究到底的心情, 来试试看。 先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
7. 静态库和动态库同名时,gcc 命令会使用哪个库文件
首先先删除除.c 和.h 外的所有文件,恢复成我们刚刚编辑完举例程序状态。
# rm -f hello hello.o /usr/lib/libmyhello.so
# ls
hello.c hello.h main.c
#
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。
# gcc -c hello.c
# ar -cr libmyhello.a hello.o (或-cvr )
# gcc -shared -fPIC -o libmyhello.so hello.o
通过最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.s o 都已经生成,并都在当前目录中。
我们运行 gcc 命令来使用函数库 myhello 生成目 标文件 hello,并运行程序 hello。# gcc -o hello main.c -L. –lmyhello
此时从程序 hello 运行的结果中可以看出,当静态库和动态库同名时,gcc 命令将优先使用动态库,将文件 libmyhello.so 复制到目录/usr/lib 中即可。
若如果直接#gcc main.c libmyhello.a -o hello
#gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了
8. Linux 下静态库.a 与.so 库文件的生成与使用
先创建一个作业目录,保存本次练习的文件。
#mkdir test2
#cd test2
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); }
1. 静态库.a 文件的生成与使用。
1.1 生成目标文件(xxx.o)
> gcc -c A1.c A2.c
1.2 生成静态库.a 文件
> ar crv libafile.a A1.o A2.o
1.3 使用.a 库文件,创建可执行程序(若采用此种方式,需保证生成的.a 文件与.c 文件保 存在同一目录下,即都在当前目录下)
gcc -o test test.c libafile.a
2. 共享库.so 文件的生成与使用
2.1 生成目标文件(xxx.o() 此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so 文件时会出错)
---> gcc -c -fpic A1.c A2.c
2.2 生成共享库.so 文件
---> gcc -shared *.o -o libsofile.so
2.3 使用.so 库文件,创建可执行程序
---> gcc -o test test.c libsofile.so
运行时发现错误,这是因为由于 linux 自身系统设定的相应的设置的原因,即其只在/lib and /usr/lib 下搜索对应 的.so 文件,故需将对应 so 文件拷贝到对应路径。
--->sudo cp libsofile.so /usr/lib
再次执行./test,即可成功运行。
二. 生成静态库文件并连接记录大小
以一个程序做介绍:
1. 建立三个文件,main.c ,sub1.c ,sub2.c
程序一:main.c
#include"sub1.c"
#include"sub2.c"
int main()
{
int a,b;
printf("inpute number please:\n");
scanf("%d%d",&a,&b);
printf("%d x %d=%f\n",a,b,x2x(a,b));
printf("%d + %d=%d\n",a,b,x2y(a,b));
return 0;
}
程序二:sub1.c
float x2x(int a,int b)
{float c;
c=a*b;
return c;}
程序三: sub2.c
int x2y(int a,int b)
{float d;
d=a+b;
return d;}
2. 静态文件生成
2.1 生成三个目标文件
gcc -c main.c sub1.c sub2.c
2.2 生成静态文件.a文件
2.3 用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序
2.4 运行如图
2.5 记录文件大小
3. 生成动态文件库
3.1 生成目标文件(这里必须添加-fpic
,否则会出错)
3.2 生成动态库.so文件创建可执行程序
3.3用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序
命令行:gcc -o main1 main.c libsofile.so
执行程序main1
3.4 记录文件的大小
静态库文件和动态库文件大小相比看出,两个可执行程序文件一样大,但通常动态库要小一些。
三. gcc常用命令使用与gcc背后故事
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL 语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。
实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)。
示例程序 test.c
//test.c #include <stdio.h>
int main(void)
{ printf("Hello World!\n"); return 0; }
1. 预处理:
gcc -E test.c -o test.i 或 gcc -E test.c
输出 test.i 文件中存放着 test.c 经预处理之后的代码。gcc 的-E 选项,可以让编译器在预处理后停止,并输出预处理结果。查看test.i
2. 编译为汇编代码:
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:
gcc -S test.i -o test.s
gcc 的-S 选项,表示在程序编译期间,在生成汇编代码后,停止,-o 输出汇编代码文件。
3.汇编(Assembly)
对于上一小节中生成的汇编代码文件 test.s,gas 汇编器负责将其编译为目标文件 gcc -c test.s -o test.o
4.连接(Linking)
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生 成可执行文件。附加的目标文件包括静态连接库和动态连接库。. 对于上一小节中生成的 test.o,将其与C标准输入输出库进行连接,最终生成程序 test.
gcc test.o -o test
5.多个程序文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理 这些编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并 最终生成可执行程序 test,可以使用下面这条命令:
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -o test
6.检错
gcc -pedantic illcode.c -o illcode
-pedantic 选项能够帮助程序员发现一些不符合 ANSI/ISO C 标准的代码,
gcc -Wall illcode.c -o illcode
GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W 开头,其中最有价值的当数-Wall 了,使用它能够使 GCC 产生尽可能多的警告信息。
gcc -Werror test.c -o test
在编译程序时带上-Werror 选项,那 么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。
7. 库文件连接
GCC 在编译时必须用自己 的办法来查找所需要的头文件和库文件。
首先我们要进行编译 test.c 为目标文件,这个时候需要执行
gcc –c –I /usr/dev/mysql/include test.c –o test.o
我们把所有目标文件链接成可执行文件:
gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test
强制链接时使用静态链接库
gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test
四. nasm汇编编译器编译
1. 用nasm编译文件nasm -f elf64 hello.asm
2. 将目标文件链接到可执行文件hello1
3. 运行该可执行文件
4. 记录文件的大小
c源代码编译形成的程序大小
对比两个文件大小,可以看出nasm编译器生成文件要比c源程序编译形成的文件要小。
五 .体验即将绝迹的bbs以及了解curse库
1.启用telnet client和适用于LINUX的windows子系统,(光标控制)
- 快捷键 Windows+r,输入control,即可进入控制面板
“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"。
2. 输入cmd即可进入命令行窗口
按要求既可体验.
2.curse库
函数 | 功能 |
---|---|
initscr() | 初始化curses库和tty |
endwin() | 关闭curses并重置tty |
refresh( | 刷新屏幕显 |
mvaddch(y,x,c) | 在坐标(y,x)处显示字符c |
mvaddstr(y,x,str) | 在坐标(y,x)处显示字符串str |
cbreak() | 开启输入立即响应 |
noecho() | 输入不回显到屏幕 |
curs_set(0) | 使光标不可见 |
attrset() | 开启图形显示模式、 |
keypad(stdscr, true) | 开启小键盘方向键输入捕捉支持 |
curse库的头文件和库文件分别安装在 user/include和usr/lib下面。
六.Linux 环境下C语言编译实现贪吃蛇游戏
- 创建文件 mysnake1.0.c, 将游戏代码写入文件 mysnake1.0.c
vim mysnake1.0.c
- 使用终端图形库curses.h文件
需要自行下载安装,Ubuntu下可以这么下载 sudo apt-get install libncurses5-dev 已经替换成ncurses.h 即 new curses.h的意思,完全兼容curses。
sudo apt-get install libncurses5-dev
- 编译链接生成可执行文件mysnake.1.0
cc mysnake1.0.c -lcurses -o mysnake1.0
- 生成贪吃蛇游戏动画
从图中可以看到,完成了一个贪吃蛇游戏的编写与运行,更多用c语言的游戏制作可以参考网上相关代码。
总结:
从这次学习GCC命令中,我们知道在GCC这一条简单的命令背后有着许多的步骤,从开始的.c文件到生成最后的可执行文件,中间也经历了许多过程。从了解静,动态库到学会运用静,动态库区链接文件,这样的收获真的很多。我还学会了编译运行一个小游戏,curse库的作用,更体验了 gcc编译的作用。一个可执行程序的组装更是多个的作用。希望在以后能更深入和熟练的掌握gcc命令。