静态库和共享库开发
再讲静态库和共享库之前先讲一下一个可执行文件的生成过程
1、预处理
①将所有的#define删除,并且展开所有的宏定义
②处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等
③处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
④删除所有注释 “//”和”/* */”. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
⑤保留所有的#pragma编译器指令以备编译器使用
⑥通常使用以下命令来进行预处理:
gcc -E test.c -o test.i
参数-E表示只进行预处理,打开test.i就可以看到预处理完成的内容。
2、编译
①编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。预处理之后,可直接对生成的test.i文件编译,生成汇编代码:
gcc -S test.i -o test.s
②gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件
3、汇编
①汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。
②汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。
③对于上面生成的汇编代码文件test.s,gas汇编器负责将其编译为目标文件,如下:
gcc -c test.s -o test.o
4、链接
①gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
②对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test
gcc test.o -o test
下面讲一下gcc的用法
gcc [选项] 文件...
选项:
-v 显示编译器调用的程序
-E 仅作预处理,不进行编译、汇编和链接 生成.i文件
-S 编译到汇编语言,不进行汇编和链接 生成.s文件
-c 编译、汇编到目标代码,进行链接,=生成.o文件
-o file 将经过gcc处理过的结果存为file,这个结果可能是预处理,汇编,目标或最终的可执行文件
-g[gdb] 在可执行文件中加入调试信息,若使用中括号中的选项,表示加入gdb扩展的调试信息,方便使用gdb进行调试
-I dirname 将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数
-L dirname 将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在连接过程中使用的参数,默认状态下在/usr/lib中找
-l name 在连接时,装载名字为libname.a的函数库
1、编写hello world,并使用脚本运行、
包含文件:main.c、run.sh
main.c:
#include<stdio.h> int main(void) { printf("Hello World!\n"); return 0; }
run.sh:
#!/bin/bash gcc -o main main.c ./main
2、预编译以下2个.c文件,并比较它们是否相同
包含文件:main1.c、main2.c、run.sh
main1.c:
#include<stdio.h> int main(void) { printf("Hello World!\n"); return 0; }
main2.c:
#include<stdio.h> #define N world int main(void) { printf("hello N"); return 0; }
run.sh:
#!/bin/bash gcc -E main1.c -o main1.i gcc -E main2.c -o main2.i diff main1.i main2.i if test $? -eq 0;then echo "file same" else echo "file different" fi
3、生成汇编代码文件,并查看
包含文件:main.c、run.sh
run.sh:
#!/bin/bash gcc -S main.c -o main.s cat main.s
4、生成目标文件,并查看
包含文件:main.c、run.sh
run.sh:
#!/bin/bash gcc -c main.c -o main.o cat main.o
5、执行链接命令,并确定文件类型,查看目标文件信息
包含文件:main.c、run.sh
run.sh:
#!/bin/bash gcc -c main.c -o main.o gcc -o main main.o file main objdump -x main
6、加法的 helloworld
包含文件:main.c、run.sh
main.c:
#include<stdio.h> int main(void) { int a=1; int b=2; printf("%d+%d=%d\n",a,b,a+b); return 0; }
run.sh:
#!/bin/bash gcc -o main main.c ./main
7、以命令参数执行加法计算
包含文件:main.c、run.sh
相关知识:
main()函数的形式
在最新的 C99 标准中,只有以下两种定义方式是正确的:
int main( void )--无参数形式 { ... return 0; }
int main( int argc, char *argv[] )--带参数形式 { ... return 0; }
int 指明了 main()函数的返回类型,函数名后面的圆括号一般包含传递给函数的信息。 void 表示没有给函数传递参数。关于带参数的形式,我们等会讨论。浏览老版本的 C 代码,将会发现程序常常以main()这种形式开始。C90标准允许这种形式,但是 C99标准不允许。因此即使你当前的编译器允许,也不要这么写。你还可能看到过另一种形式:void main()有些编译器允许这种形式,但是还没有任何标准考虑接受它。C++ 之父Bjarne Stroustrup 在他的主页上的 FAQ 中明确地表示:void main( ) 的定义从来就不存在于 C++ 或者 C 。所以,编译器不必接受这种形式,并且很多编译器也不允许这么写。坚持使用标准的意义在于:当你把程序从一个编译器移到另一个编译器时,照样能正常运行。
main()函数的返回值
从前面我们知道 main()函数的返回值类型是 int 型的,而程序最后的return 0; 正与之遥相呼应,0就是 main()函数的返回值。那么这个0返回到那里呢?返回给操作系统,表示程序正常退出。因为return语句通常写在程序的最后,不管返回什么值,只要到达这一步,说明程序已经运行完毕。而 return的作用不仅在于返回一个值,还在于结束函数。
main()函数的参数
C 编译器允许 main()函数没有参数,或者有两个参数(有些实现允许更多的参数,但这只是对标准的扩展)。这两个参数,一个是 int 类型,一个是字符串类型。第一个参数是命令行中的字符串数。按照惯例(但不是必须的),这个 int 参数被称为 argc(argument count)。大家或许现在才明白这个形参为什么要取这么个奇怪的名字吧,呵呵!至于英文的意思,自己查字典吧。第二个参数是一个指向字符串的指针数组。命令行中的每个字符串被存储到内存中,并且分配一个指针指向它。按照惯例,这个指针数组被称为 argv(argument value)。系统使用空格把各个字符串格开。一般情况下,把程序本身的名字赋值给 argv[0],接着,把最后的第一个字符串赋给 argv[1],等等。
main.c:
#include<stdio.h> #include<stdlib.h> int main(int argc,char *argv[]) { if(argc!=3){return 0;printf("error");} int a=atoi(argv[1]); int b=atoi(argv[2]); printf("%d+%d=%d\n",a,b,a+b); return 0; }
run.sh:
#!/bin/bash gcc -o main main.c ./main 1 2
8、编写 add 函数实现加法计算
包含文件:main.c、run.sh
main.c
#include<stdio.h> int add(int p1,int p2) { int c; c=p1+p2; return c; } int main(int argc,char *argv[]) { if(argc!=3){ printf("error\n"); return 0; } else{ int a=atoi(argv[1]); int b=atoi(argv[2]); printf("%d+%d=%d\n",a,b,add(a,b)); } return 0; }
run.sh
#!/bin/bash gcc -o main main.c ./main 3 5
9、静态链接编译与动态链接编译的区别
包含文件:main.c、run.sh
相关知识:
全静态:不会发生应用程序在不同linux版本下的标准库不兼容问题,但是生成的文件比较大,应用程序功能受限(不能调用动态库等)
全动态:生成文件小,但是容易发生不兼容问题
main.c:
#include<stdio.h> int add(int p1,int p2) { int c; c=p1+p2; return c; } int main(int argc,char *argv[]) { if(argc!=3){ printf("error\n"); return 0; } else{ int a=atoi(argv[1]); int b=atoi(argv[2]); printf("%d+%d=%d\n",a,b,add(a,b)); } return 0; }
run.sh:
#!/bin/bash gcc -static main.c -o main_static gcc main.c -o main_dynamic file main_static file main_dynamic objdump -x main_static|grep NEEDED objdump -x main_dynamic|grep NEEDED
10、制作静态库
静态库是目标文件的打包,可通过 ar 命令进行制作。生成的静态库名字应该是 lib***.a,否则 gcc 命令无法识别。
包含文件:lib/add.c、include/add.h、main.c、run.sh
main.c:
#include<stdio.h> #include"add.h" int main(int argc,char *argv[]) { if(argc!=3){ printf("error\n"); return 0; } else{ int a=atoi(argv[1]); int b=atoi(argv[2]); printf("%d+%d=%d\n",a,b,add(a,b)); } }
./include/add.h:
int add(int p1,int p2);
./lib/add.c:
int add(int p1,int p2) { int c; c=p1+p2; return c; }
run.sh:
#!/bin/bash gcc -c -I./include lib/add.c -o lib/add.o ar -rsv lib/libadd.a lib/add.o gcc -o main main.c -I./include -L./lib -ladd ./main 3 5
11、制作共享库
包含文件:lib/add.c、include/add.h、main.c、run.sh
main.c:
#include<stdio.h> #include"add.h" int main(int argc,char *argv[]) { if(argc!=3){ printf("error\n"); return 0; } else{ int a=atoi(argv[1]); int b=atoi(argv[2]); printf("%d+%d=%d\n",a,b,add(a,b)); } return 0; }
./include/add.h:
int add(int p1,int p2);
./lib/add.c:
int add(int p1,int p2) { int c; c=p1+p2; return c; }
run.sh:
#!/bin/bash gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so gcc -o main main.c -I./include -L./lib/ -ladd echo $LD_LIBRARY_PATH export LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH ./main 1 2
12、以多种方式实现共享库的运行
包含文件:lib/add.c、include/add.h、main.c、run1.sh、run2.sh、run3.sh
run1.sh:
#!/bin/bash gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so gcc -o main1 main.c -I./include -L./lib/ -ladd echo $LD_LIBRARY_PATH export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH ./main1 1 2
run2.sh:
#!/bin/bash gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so unset LD_LIBRARY_PATH gcc -I./include -L./lib -Wl,-rpath=./lib -o main2 main.c -ladd ./main2 1 2
run3.sh:
#!/bin/bash gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so sudo cp ./lib/libadd.so /usr/lib sudo chmod 0755 /usr/lib/libadd.so sudo ldconfig gcc -o main3 main.c -I./include -L./lib/ -ladd ./main3 1 2
13、升级替换共享库
共享库的优势是只需要针对相应的库重新编译升级,而不需要重新编译生成可执行文件,保证相互独立性。
本题不需要main.c,直接在脚本中借用11题的main.c
包含文件:lib/add.c、include/add.h、run.sh
./lib/add.c:
int add(int p1,int p2) { int c; c=p1&p2; return c; }
./include/add.h:
int add(int p1,int p2);
run.sh:
#!/bin/bash gcc -fPIC -shared -I./include lib/add.c -o lib/libadd.so #设置环境变量 export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH ../11/main 1 4
14、动态加载动态库
本题不需要lib和include,直接借用12题和13题的动态库
包含文件:main.c、run.sh
main.c:
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char **argv)
{
void *lib_handle;
int (*add)();
char *error;
if(argc!=4){
printf("error"); return 0;
}
/*打开一个动态链接库*/
lib_handle = dlopen(argv[3], RTLD_LAZY);
if (!lib_handle){
fprintf(stderr, "%s\n", dlerror());
return 1;
}
/*获取函数地址和变量地址*/
add=dlsym(lib_handle,"add");
/*当动态链接库操作函数执行失败时,dlerror 可以返回出错信息,返回值为NULL 时表示操作函数执行成功。*/
if ((error = dlerror()) != NULL){
fprintf(stderr, "%s\n", error);
return 1;
}
int a=atoi(argv[1]);
int b=atoi(argv[2]);
printf("%d+%d=%d\n",a,b,add(a,b));
/*关闭指定句柄的动态链接库*/
dlclose(lib_handle);
return 0;
}
run.sh:
#!/bin/bash gcc -o main main.c -ldl echo use the "+" ./main 2 3 ../12/lib/libadd.so echo use the "&" ./main 2 3 ../13/lib/libadd.so
15、检查系统环境是否满足依赖库要求
ldconfig -p 可以输出当前系统环境中缓冲加载的动态链接库。
包含文件:lib/add.c、include/add.h、main.c、run.sh
main.c:
#include <stdio.h> #include <stdlib.h> #include "add.h" int main(int argc, char *argv[]) { puts("this is a static libarary test ...."); int a=atoi(argv[1]); int b=atoi(argv[2]); int sum=add(a,b); printf("%d + %d = %d \n",a,b,sum); return 0; }
run.sh:
#!/bin/bash #objdump -x main |grep NEEDED |sed -n '1,$p'|awk '{print $2}'>main1.txt ldconfig -p >main2.txt #打印出当前缓存文件所保存的所有共享库的名字. for line in `objdump -x main |grep NEEDED |sed -n '1,$p'|awk '{print $2}'` do cat main2.txt |grep -q "$line" if [ $? -eq 0 ];then echo -e "$line is met \n" else echo -e "$line is not met \n" fi done