目录
(1)编译生成的子程序hello.c,hello.o,main.c
本篇文章紧接着上一篇 Ubuntu 18.04 Desktop 下的基础操作,讲到的Ubuntu18.04的一些基础操作,包括使用gcc完成c语言的编写,本篇文章讲进一步探讨利用gcc生成静态库(.a文件)与动态库(.so文件)的基础操作。其中:
- 静态库
在程序编译时会被连接到目标代码中,程序运行是则不需要静态库的存在。 - 动态库
在程序编译时不会被连接到目标代码中,而是程序运行时载入的。
两者区别:前者是编译连接的,后者是程序运行载入的。
一、GCC生成静态库(.a文件)
(1)编译生成的子程序hello.c,hello.o,main.c
首先,我们利用vim(也可以使用nano或者是gedit文本编译器)生成所需要的3个文件。
程序1:hello.h (vim 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)将hello.c 编译程 .o 文件
gcc -c hello.c
(3)由.a 文件生成静态库
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是 libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。在系统提示符下键入以下命令将创建静态库文件libmyhello.a 。
ar -crv libmyhello.a hello.o
此时,利用:
gcc -o hello main.c -L. -lmyhello
在程序中使用静态库,./hello 查看文件,是否可以正常输出:
注意:对于自定义的静态库,main.c还可以放在-L.和-lmyhello之间,否则myhello没有定义。
-L.:表示连接的库在当前目录
二、GCC生成动态库(.so文件)
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。
在之前的操作上,利用hello.o文件生成动态库
gcc -shared -fPIC -o libmyhello.so hello.o
(1)在程序中使用动态库
与静态库的使用方法一样。
gcc -o hello main.c -L. -lmyhello
此时,提示编译会出错。因为找不到动态库文件libmyhello.so。程序在运行时,会在lusrlib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件mv libmyhello.so /usr/lib 复制到目录/usr/lib中,于是:
三、综合实例
要求:在第一篇文章的程序代码基础进行改编,除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小
x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
(1)代码准备:
sub1.c
float x2x(int a,int b)
{
float c=0;
c=a+b;
return c;
}
sub2.c
float x2y(int a,int b)
{
float c=0;
c=a/b;
return c;
}
sub.h
#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif
main.c
#include<stdio.h>
#include"sub.h"
void main()
{
int a,b;
printf("Please input the value of a:");
scanf("%d",&a);
printf("Please input the value of b:");
scanf("%d",&b);
printf("a+b=%.2f\n",x2x(a,b));
printf("a/b=%.2f\n",x2y(a,b));
}
sub1.o和sub2.o的生成:
gcc -c sub1.o sub2.o
(2)静态库
ar crv libsub.a sub1.o sub2.o
(3)动态库
gcc -shared -fPIC -o libsub.so sub1.o sub2.o
gcc -o main main.c libsub.so
四、GCC常用命令
1、简单编译
通过以上静态库与动态库的基本操作学习,大家对gcc编译器肯定有了一些初步的认识,明白了它的一些基本操作,本文章的风格是从难到易,由上而下的去学习gcc操作,下面我们将讲解为什么gcc可以这样用:
(1)简单编译
//test.c
#include <stdio.h>
int main(void)
{
printf("Hello World!\n");
return 0;
}
利用vim编译器,生成test.c文件,可直接用 gcc test.c -o test 将其转化为汇编语言形式。因为实际上上述的编译过程分为预处理、编译、汇编和连接四个阶段进行
(2)预处理
gcc -E test.c test.i
//或
gcc -E test.c
可以输出 test.i文件中存放着test.c经预处理之后的代码。打开test.i文件,看一看,就明白了。后面那条指令,是直接在命令行窗口中输出预处理后的代码.
gcc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将stdio.h文件中的内容插入到test.c中了。
(3)编译其为汇编语言
gcc -S test.i -o test.s
此时,gcc 中的-S选项,表示在程序编译期间,生成汇编语言后,停止,-o 为输出为汇编文件。
(4)汇编
gcc -c test.s -o test.o
gas汇编器负责将其转化为汇编目标文件。
(5)连接
gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test
gcc test.o -o test
此时即可使用./test ,输出测试程序HelloWorld:
2、多个文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。假设有一个由test1.c和 test2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序test,可以使用下面这条命令:
gcc test1.c test2.c -o test
如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。
3、检错
gcc -pedantic illcode.c -o illcode
-pedantic编译选项并不能保证被编译程序与ANSMSOC标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。或者换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSIISO C标准的代码,但不是全部,事实上只有ANSIISOC语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。
除了-pedantic之外,GCC还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使GCC产生尽可能多的警告信息。
gcc -Wall illcode.c -o illcode
GCC给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -werror test.c -o test
5、总结
以上通过三个程序用gcc生成静态库和动态库的练习过程,以及gcc基本操作,基本上能够熟练的生成静态库和动态库并且理解使用gcc基本操作。虽然在过程中会遇到一些小问题,但是很快就解决了,这种问题多是不熟练导致。只要慢慢多练几遍,便很快能够掌握。
在学习过程中,不理解的需要及时查阅资料,目前来说。Ubuntu 18.04的资料是极其多的,及其完善的,我们也要培养这样的习惯,这样遇到深层次的问题才更容易解决。