Linux系统中关于gcc生成静态库和动态库,库函数的基础操作(编译器背后的故事)以及相关库的使用,程序游戏介绍
一.源程序到可执行程序的过程
一个源程序到一个可执行程序的过程,分为以下四步:预编译、编译、汇编、链接。其中,编译是主要部分,其中又分为六个部分:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化,链接中,分为静态链接和动态链接两个部分。同样在在gcc的编译流程中通常认为也是四个步骤:(1)预处理,(2)编译,(3)汇编,(4)链接,实际上就是将命令分成4步执行。
二.练习(关于用gcc生成静态库和动态库)
(1)练习1
链接: 练习PDF文件.
我们通常把一些公用函数制作成函数库,供其它程序使用。
函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,
程序运行时将不再需要该静态库。
动态库在程序编译时并不会被连接到目标代码中,
而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
(静态库)
(1)先创建一个作业目录并进入该目录,保存本次练习的文件。
mkdir test1
cd test1
(2)然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件。(此处用vim)
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;
}
(3)将 hello.c 编译成.o 文件,并用ls命令查看
(4)由.o 文件创建静态库,并用ls查看生成的文件。
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件
libmyhello.a。
创建静态库文件代码:
ar -crv libmyhello.a hello.o
(5)在程序中使用静态库(此处有三种方法,我演示第一种)
方法一:
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
动态库连接时也可以这样做。
(6)删除静态库文件公用函数 hello。
rm libmyhello.a
程序照常运行,静态库中的公用函数已经连接到目标文件中了。
(动态库)
(1)由.o 文件创建动态库文件。
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其
文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyh
ello.so。用 gcc 来创建动态库。
gcc -fpic -c hello.c
gcc - shared -fPIC -o libmyhello.so hello.o
( 动态库那里,一些人的系统是ubuntu16,从hello.c 生成 hello.o,需要将 gcc -c hello.c 换成 gcc -fpic -c hello.c)
(2)在程序中使用动态库
gcc -o hello main.c -L. -lmyhello
(或 gcc main.c libmyhello.so -o hello
不会出错(没有 libmyhello.so 的话,会出错),但是接下来./hello 会提示出错,因为虽然连接时用的是当前目录的动态库,但是运行时,是到/usr/lib 中找库文件的,将文件 libmyhello.so 复制到目录/usr/lib 中就 OK 了)
./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
(此处报错,原来是找不到动态库文件 libmyhello.so。程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libmyhello.so 复制到目录/usr/lib 中,再试试。)
mv libmyhello.so /usr/lib
成功啦!!!!!!
(3)假设那当静态库和动态库同名时,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
gcc -shared -fPIC -o libmyhello.so hello.o
ls
hello.c hello.h hello.o libmyhello.a libmyhello.so main.c
通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.so 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目标文件 hello,并运行程序 hello。
gcc -o hello main.c -L. –lmyhello
(动态库和静态库同时存在时,优先使用动态库,当然,如果直接的话,就是指定为静态库了)
gcc main.c libmyhello.a -o hello
./hello
./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory
从程序 hello 运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib中即可。
(2)练习2 Linux 下静态库.a 与.so 库文件的生成与使用(与练习一类似)
(1)与前者相似创建目录,然后用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的四个文件 A1.c 、 A2.c、 A.h、test.c
(2)静态库.a 文件的生成与使用。
(3)使用.a 库文件,创建可执行程序(若采用此种方式,需保证生成的.a 文件与.c 文件保存在同一目录下,即都在当前目录下)
(4)动态库,生成目标文件(xxx.o() 此处生成.o 文件必须添加"-fpic"(小模式,代码少),否则在生成.so文件时会出错)
运行 ldd test,查看链接情况
这是由于 linux 自身系统设定的相应的设置的原因,即其只在/lib and /usr/lib 下搜索对应的.so 文件,故需将对应 so 文件拷贝到对应路径。
成功啦!!!!!!!!!
(3) 练习3(紧接上次的练习进行操作: 链接.)
(1)增添新的函数x2y
(2)用gcc分别编译为3个.o 目标文件
(3)将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件
(4)然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序
(5)将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件,步骤与练习一一致
(6)记录文件的大小,并与之前做对比。
通过比较发现静态库要比动态库要小很多,生成的可执行文件大小只存在较小差距
三.gcc编译工具集中各软件的用途,了解EFF文件格式,汇编语言格式。
(1)gcc常用命令
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
gcc常用编译代码
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:用 来将程序 地址转 换成其所 对应的程 序源文 件及所对 应的代 码 行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对 应的源代码位置。
单文件编译
示例:
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
一步到位指令:
gcc test.c -o test
实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)。
1.预处理
gcc -E test.c -o test.i
或者
gcc -E test.c
2.编译为汇编代码
gcc -S test.i -o test.s
3.汇编
gcc -c test.s -o test.o
4.链接
gcc test.o -o test
在命令行窗口中,执行./test, 让它说 HelloWorld 吧!
多文件编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理这些编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 test,可以使用下面这条命令:
gcc test1.c test2.c -o test
如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深究起
来,上面这条命令大致相当于依次执行如下三条命令:
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -o test
检错
gcc -pedantic illcode.c -o illcode-pedantic
编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助Linux 程序员离这个目标越来越近。或者换句话说,-pedantic 选项能够帮助程序员发现一些不符合
ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall 了,使用它能够使 GCC 产生尽可能多的警告信息。
gcc -Wall illcode.c -o illcode GCC
给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的 Linux 程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -Werror test.c -o test
编译成可执行文件
首先我们要进行编译 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 在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链
接库,如果需要的话可以在编译时加上-static 选项,强制使用静态链接库。
在/usr/dev/mysql/lib 目录下有链接时所需要的库文件 libmysqlclient.so 和 libmysqlclient.a,为了让
GCC 在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test
静态库链接时搜索路径顺序:
- ld 会去找 GCC 命令中的参数-L
- 再找 gcc 的环境变量 LIBRARY_PATH
- 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初 compile gcc 时写在程序内的
动态链接时、执行时搜索路径顺序: - 编译目标代码时指定的动态库搜索路径
- 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径
- 配置文件/etc/ld.so.conf 中指定的动态库搜索路径
- 默认的动态库搜索路径/lib
- 默认的动态库搜索路径/usr/lib
(2)GCC编译器背后的故事
GCC编译器背后的故事.提取码:wlzy
(3)as汇编编译器
1.安装
sudo apt install nasm
2.使用nasm对示例代码“hello.asm”编译生成可执行程序
创建hello.asm文件
内部代码:
; 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 ; 调用内核功能
3.编译输出:
4.与“hello world”C代码的编译生成的程序大小进行对比
hello.asm文件编译输出后的大小:
“hello world”C代码的编译生成的程序大小:
四.Linux中的第三方库函数
(1)Linux 系统中终端程序最常用的光标库的主要函数功能
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.
(3)体验即将绝迹的远古时代的 BBS (一个用键盘光标控制的终端程序)
1.在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net。
2.找到以下两个文件勾选:
1).
2).
3.打开一个cmd命令行窗口(win+r 输入cmd即可进入)命令行输入 telnet bbs.newsmth.net,即可体验
(4)curses库的安装
1.在Ubuntu中用 sudo apt-get install libncurses5-dev 安装curses库
2.头文件(比如curses.h)和库文件的安装目录
curses函数库的头文件和库文件就被分别安装在/usr/include/和/usr/lib/下