编译器背后的故事
一、用gcc生成静态库和动态库
函数库分为静态库和动态库两种,静态库编译时就已经连接目标文件,运行时不需调用,而动态库是运行时才调用。为了探究其如何运作的,因此在linux中用gcc命令实现静态库和动态库的创建
第一步:创建一个新的目录存放文件
接下来创建hello.h、hello.c 和 main.c三个文件
用gedit编译三个文件
文件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命令将hello.c文件编译成.o文件,并用ls命令查看是否生成文件
第 3 步:由.o 文件创建静态库
用 ar 命令创建文件名为libmyhello.a的静态库
第 4 步:在程序中使用静态库
第 5 步:由.o 文件创建动态库文件
创建名为 myhello的动态库,则动态库文件名就是 libmyh ello.so。用 gcc 来创建动态库。
第 6 步:在程序中使用动态库
我 们先运行 gcc 命令生成目标文件,再运行它看看结果
可以看到程序报错了,这是因此找不到动态库文件 libmyhello.so,因此我们需要将文件 libmyhello.so 复制到目录/usr/lib 中
移动又发现该文件移动不了,提示没有权限,因此采用sudo命令来移动,移动后运行文件
可以看到该文件成功运行
那当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?
先删除除.c 和.h 外的所有文件
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so
运行 gcc 命令来使用函数库 myhello 生成目 标文件 hello,并运行程序 hello
从程序 hello 运行的结果中知道,当静态库和动态库同名时,gcc 命令将优先使用动态库。
二、静态库.a与.so库文件的生成与使用
还是先创建一个新的目录来保存文件
接下来创建四个文件 A1.c 、 A2.c、 A.h、 test.c并分别进行gedit编译
文件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);
}
生成目标文件
生成静态库.a 文件
使用.a 库文件,创建可执行程序
共享库.so 文件的生成与使用,先生成目标文件,再生成共享库.so 文件,然后使用.so 库文件,创建可执行程序并运行
结果发现程序报错,这是因为找不到.so文件,因此需将对应 so 文件拷贝到对应路径/usr/lib下,并执行命令
成功执行命令。
三、在实践中体验创建静态库
先创建一个新目录test3存放文件
分别创建main.c、main1.c、main2.c三个文件并用gedit编辑。其中main1.c放x函数运算a和b的相加,main2.c放y函数运算a和b的相乘,main.c中调用两个函数并输出结果。
代码如下:
main.c
#include"main1.c"
#include"main2.c"
#include<stdio.h>
int main()
{
float a=2;
float b=5;
printf("a+b=%f\n",x(a,b));
printf("a*b=%f\n",y(a,b));
return 0;
}
main1.c
#include<stdio.h>
float x(float a,float b)
{
return (a+b);
}
main2.c
#include<stdio.h>
float y(float a,float b)
{
return(a*b);
}
然后用gcc命令将三个文件编译为.o的目标文件,并用ls查看是否生成目标文件
之后将main1.c和main2.c目标文件用 ar工具生成1个 .a 静态库文件
然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序
运行main文件,得到了我们想要的结果
静态库的实践成功了!
四、在实践中体验动态库的创建
将main1.c、main2.c目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序
从结果看到动态库的创建成功了。
五、Linux GCC常用命令
先创建test.c文件
输入下列代码:
#include <stdio.h>
int main(void)
{ printf("Hello World!\n");
return 0;
}
一步到位的编译:
实质上,上述编译过程是分为四个阶段进行的,即预处理、编译 、汇编 和连接
下面是四个部分的命令:
执行文件的输出与之前的是一样的。
假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 test,可以使用下面这条命令:
gcc test1.c test2.c -o test
上面这条命令大致相当于依次执行如下三条命令
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc test1.o test2.o -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
在/usr/dev/mysql/lib 目录下有链接时所需要的库文件 libmysqlclient.so 和 libmysqlclient.a,为了让 GCC 在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test
静态库链接时搜索路径顺序:
1.ld 会去找 GCC 命令中的参数-L
2. 再找 gcc 的环境变量 LIBRARY_PATH
3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初 compile gcc 时写在程序内的
动态链接执行时搜索路径顺序:
1.编译目标代码时指定的动态库搜索路径
2. 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径 3. 配置文件/etc/ld.so.conf 中指定的动态库搜索路径
4. 默认的动态库搜索路径/lib
5.默认的动态库搜索路径/usr/lib
六、GCC编译器背后的故事
为了能够演示编译的整个过程,先创建一个工作目录 test0,然后用文本编辑器生成一个C语言编写的简单 hello.c 程序
源代码:
#include <stdio.h>
int main(void)
{
printf("Hello World! \n");
return 0;
}
1、编译过程
1.预处理
预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编 译指令,比如#if #ifdef #elif #else #endif 等。 (2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。 (3) 删除所有注释“//”和“/* */”。 (4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。 (5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
使用 gcc 进行预处理的命令如下:
gcc -E hello.c -o hello.i
2.编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及 优化后生成相应的汇编代码。
使用 gcc 进行编译的命令如下:
gcc -S hello.i -o hello.s
3.汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o 的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相 对于编译过程比较简单,通过调用 Binutils 中的汇编器 as 根据汇编指令和处理 器指令的对照表一一翻译即可。 当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标 文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部 分了,但是在链接之前还不能执行。
使用 gcc 进行汇编的命令如下:
gcc -c hello.s -o hello.o
4.链接
链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行 文件会比较大。链接器将函数的代码从其所在地,拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完 成的主要任务是:符号解析和重定位。(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
使用动态库进行链接:
gcc hello.c -o hello
使用size命令查看文件大小
使用ldd命令查看链接的库
使用静态库进行链接
gcc -static hello.c -o hello
可以看到与静态库链接产生的可执行文件比与动态库链接产生的文件大得多
2、分析 ELF 文件
查看其各个 section 的信息:
readelf -S hello
对其进行反汇编如下:
objdump -D hello
七、安装nasm并用其编译成可执行程序
先安装nasm
之后编译成可执行程序并运行
查看文件的大小
与前面的相比小了。
八、实际程序是如何借助第三方库函数完成代码设计
1、了解Linux 系统中终端程序最常用的光标库(curses)的主要函数功能
cbreak():调用cbreak函数后,除了“Del”和“Ctrl”键外,接受其他所有字符输入。
nl()/nonl():输出时,换行是否作为回车字符。nl函数将换行作为回车符,而nonl函数相反。
noecho()/echo():关闭/打开输入回显功能。
intrflush(WINDOW *win, bool bf):win为标准输出。当bf为true时输入Break,可以加快中断的响应。但是,有可能会造成屏幕输出信息的混乱。
keypad(WINDOW *win, bool bf):win为标准输出。调用keypad函数后,将可以使用键盘上的一些特殊字符,如方向键,转化成curses.h中的特殊键。
refresh():重绘屏幕显示内容。在调用initscr函数后,第一次调用refresh函数会清除屏幕显示。
这是我参考的资料链接,完整的可以顺着链接过去
https://blog.csdn.net/wp1995/article/details/53223379
2、以游客身份体验一下即将绝迹的远古时代的 BBS
3、安装curses库
接下来我们查看安装在哪
可以发现头文件在/usr/include 目录下。
九、Linux 环境下C语言编译实现贪吃蛇游戏
代码片段
想看完整版的小伙伴可以点下方链接查看
http://www.linuxidc.com/Linux/2011-08/41375.htm
程序运行结果
十、总结
经过这次gcc命令创建静态库和动态库实践,让我对程序的编译过程有了更深的了解,而且通过对程序编译的一步步分解,原来一直用的编译命令是一步到位的,其实是经历了几个不同的编译过程。在最后编译贪吃蛇游戏后不仅收获了乐趣还收获了知识,一个优秀的程序是依靠代码库的,希望以后多加练习掌握更多的linux知识。