linux编程学习笔记(四) 编译工具和动态库

1 gcc 通用选项

-o 输出的文件名
-O -O1 -O2 -O3 编译优化 -O默认情况下是最优化的
-g -g0 -g1 -g2 -g3  产生调试信息
-W 两个选项all  error   
-Wall 显示所有警告  
-Werror 所有警告当错误
-w 关闭警告
-E 预编译  直接会打印出来 所以一般和-o配合使用
gcc map.c -E -o map.i
-c 只编译不连接
gcc map.c -c -o map.o
-S 汇编 
gcc map.c -S -o map.s

编译过程 -E -c -S 自动调用连接器ld


-D 在命令行定义宏 

int printf(const char*,...);
	main()
	{
		printf("%d\n",NUM);
	}

	zhao@ubuntu:~/unix/two$ gcc gcc.c -DNUM=50
	zhao@ubuntu:~/unix/two$ ./a.out 
	50

还可以在代码中定义宏


-x 指定编译的语言类型
c
assembler 
gcc -xassembler map.s
none 自动判定
gcc -xnone map.c

-std= 设置c语言标准
-std=c89
-std=c99
c99一些特性:
restrict 关键字 优化指针寻址
告诉编译器 所有修改该指针指向内存的操作都必须通过该指针,以便于编译器进行优化。
但判断有没有其他指针或其他方式修改该内存,是程序员的工作。
for(int i=0;i <10;i++) { }  for中可以设置变量了
main 需要返回int,否则警告


补充 文件类型
.so 动态库
.a 静态库
.i  预编译文件
.o 二进制文件
.s 汇编文件




2 静态库的编译(.a)

不使用库的时候直接编译可执行程序
gcc callku.c ku1.c ku2.c -omain

2.1编译成目标文件

-static  可选

gcc -static -c 代码.c        生成.o文件
gcc -static -c ku1.c
gcc -static -c ku2.c

2.2归档成静态库

ar工具(创建归档文件)
ar -r 静态库文件 被归档的文件
zhao@ubuntu:~/unix/two$ ar -r libku.a ku1.o  ku2.o
ar: creating ku.a
ar -t 静态库文件 查看归档文件包含
zhao@ubuntu:~/unix/two$ ar -t libku.a 
ku1.o
ku2.o

nm工具 (查看函数符号表)
nm 静态库或者动态库或者目标文件或者执行文件
zhao@ubuntu:~/unix/two$ nm libku.a  可以看到目标中包含的函数
ku1.o:
00000000 T add


ku2.o:
00000000 T sub


2.3使用静态库

gcc 代码 库归档
gcc callku.c libku.a -omain 
实际上是将静态库作为代码的一部分在编译
gcc callku.c -lku -L.
标准调用方式
如果其他组的人写了很多的文件,那么你使用他们的函数时要编译大量的代码
这时不如使用他们的库文件

2.4 使用静态库完成如下程序:

输入一个菱形的半径,打印一个菱形
输入一个整数,封装成IOTool
菱形的打印封装成Graphics
计划:
1 实现输入
2 实现菱形
3 编译成静态库
4  调用静态库

IOTool.c

#include <stdio.h>
int inputInt(const char *info)
{
	printf("%s:",info);
	int ret;
	scanf("%d",&ret);
	return ret;
}

Graphics.c

#include <stdio.h>
void diamond(int r)
{
	int x,y;
	for(y=0;y<=2*r;y++)
	{
		for(x=0;x<=2*r;x++)
		{
			if(y==x+r || y==x-r || y==-x+r || y==3*r-x)
			{
				printf("*");
			}
			else
			{
				printf(" ");
			}
			
		}
		printf("\n");
	}
}
call_diaomond.c

main()
{
	int r =  inputInt("请输入菱形的半径");
	 diamond( r);
}

gcc -static -c  Graphics.c 
gcc -static -c IOTool.c
ar -r libdemo.a Graphics.o IOTool.o

gcc  call_diaomond.c libdemo.a   //这里要先写代码 后写库 交换会无法正确编译
zhao@ubuntu:~/unix/two$ ./a.out 

请输入菱形的半径:3
   *   
  * *  
 *   * 
*     *
 *   * 
  * *  
   *  

2.5总结

1什么是库?
函数等代码封装的二进制已编译的归档文件
2 ar归档工具
3 采用库的方式管理代码优点:
容易组织代码
复用
保护代码版权
4 静态库的静态含义:
编译好的程序运行的时候不依赖库
库作为程序的一部分编译和连接
5 静态库本质:
就是目标文件的集合(归档)
6 -static 可选 (可以不写)

库命名规则:
lib库名.a.主版本号.副版本号.批号
lib库名.a
库的使用规则:
-l库名(不写lib) -L制定库所在路径
gcc call_diaomond.c -ldemo -L.
这样的调用方式需要你使用库的标准命名规则(lib库名.a)




3 动态库的编译

3.1 什么是动态库?

动态库是可以执行的,静态库不能执行(静态库只编译未连接)
但动态库没有main,不能独立执行
动态库不会连接成程序的一部分
程序执行的时候,必须需要动态库文件。

3.2工具

ldd 查看程序需要调用的动态库文件ldd 只能查看可执行文件(ELF格式,
file 查看文件格式 
readelf -h 查看elf文件头格式
nm 查看库中的函数符号

3.3动态库的编译

3.1 编译
-c -fpic(可选)  position indenpendet code 
gcc -c -fpic IOTool.c 
 gcc -c -fpic Graphics.c
3.2 连接
-shared
gcc -shared  IOTool.o Graphics.o -o libdemo2.so 

3.4 使用动态库

gcc 代码 动态库文件名
gcc 代码 -l库名  -L动态库路径
gcc call_diaomond.c  -ldemo2 -L.
问题:
1 执行程序怎么加载动态库?
找到动态库
加载动态库到内存
映射到用户的内存空间
系统对动态库查找规则:
/lib 
/usr/lib
到环境变量LD_LIBRARY_PATH指定的路径查找
缓冲机制:
预先把/lib:/usr/lib:LD_LIBRARY_PATH中的动态库加载到缓存中
ldconfig -v 刷新缓存中的动态库
ldconfig -v | grep "libdemo2.so" 查看缓存中是否有需要的库

2 动态库没有作为程序的一部分,为何在连接时需要它?
连接器需要确定函数在动态库中的偏移位置

3 cannot open shared object file  解决方法:
zhao@ubuntu:~/unix/two$ gcc call_diaomond.c  -ldemo2 -L.
zhao@ubuntu:~/unix/two$ ./a.out 
./a.out: error while loading shared libraries: libdemo2.so: cannot open shared object file: No such file or directory
zhao@ubuntu:~/unix/two$ ldd a.out 
linux-gate.so.1 =>  (0x00242000)
libdemo2.so => not found
libc.so.6 => /lib/libc.so.6 (0x00dc1000)
/lib/ld-linux.so.2 (0x0055a000)
编译时指定的路径是做为连接使用。
因为动态库使用时,只在上述3个位置中查找。(/lib /usr/lib LD_LIBRARY_PATH)
此时在将动态库拷贝到前两个路径之一去可解决。(需要管理员权限)
或者修改环境变量
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH (冒号隔开多个路径,只在当前terminal中有效)
zhao@ubuntu:~/unix/two$ gcc call_diaomond.c  -ldemo2 -L. 



-L  与 LD_LIBRARY_PATH的区别:

-L是在编译时将查找.so 或者 .a 连接生成需要的程序

LD_LIBRARY_PATH是环境变量, 程序运行时查找库



3.5 总结

动态库编译与使用方法:
1编译
gcc -c -fpic IOTool.c 
 gcc -c -fpic Graphics.c
2连接
gcc -shared -olibdemo2.so IOTool.o Graphics.o
3 使用(注意动态库的路径)
gcc call_diaomond.c  -ldemo2 -L.


3.6 综合应用

输入两个数 计算两个数的和
要求:输入与计算两个数的和封装成动态库的调用

//InputInt.c
	#include <stdio.h>
	void inputInt(int *a)
	{
		printf("请输入两个整数");
		scanf("%d%d",a,a+1);
	}
// sum.c
	 int sum(int a,int b)
	{
		return a+b;
	}
	

// call_add.c
	 #include <stdio.h>
	main()
	{
		int a[2];
		inputInt(a);
		printf("%d+%d=%d\n",a[0],a[1],sum(a[0],a[1]));
	}

gcc -fpic -c InputInt.c
gcc -fpic  -c sum.c
gcc -shared  -o libdemo3.so InputInt.o sum.o
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

gcc call_add.c -ldemo3 -L.
zhao@ubuntu:~/unix/two$ ./a.out 
请输入两个整数



4 使用libdl.so库

调用动态库有两种方式:
1 像前面一样 通过-l -L来由系统调用
2 手动调用动态库


动态库加载的原理

动态库中的函数的查找已经封装成库 libdl.so
dlopen 打开一个动态库
dlsym 在打开动态库中找一个函数
dlclose 关闭动态库
dlerror 返回错误


      #include <dlfcn.h>
      Link with -ldl.
       void *dlopen(const char *filename, int flag);
filename :动态库路径
        flag :RTLD_LAZY 使用时加载 ,RTLD_NOW 立即加载
       返回值 void* 返回动态库句柄
      
       void *dlsym(void *handle, const char *symbol);
handle 已打开的动态库的句柄
symbol 函数名字
返回值: 返回函数指针
       int dlclose(void *handle);
       不是用库后 关闭动态库
       char *dlerror(void);
   

       dldemo.c
       #include <dlfcn.h>
	main()
	{
		void *handle = dlopen("./libdemo2.so",RTLD_LAZY); //注意这里不能写静态库
		void (* func)(int) = dlsym(handle,"diamond");
		func(5);
		dlclose(handle);
		
	}



zhao@ubuntu:~/unix/two$ gcc dldemo.c -ldl
zhao@ubuntu:~/unix/two$ ./a.out 
     *     
    * *    
   *   *   
  *     *  
 *       * 
*         *
 *       * 
  *     *  
   *   *   
    * *    
     *     


总结:
1 编译连接动态库
2 使用动态库
3 怎么配置让程序调用动态库
4 掌握某些工具的使用: nm ldd lddconfig
objdump
strip 去掉多余信息

zhao@ubuntu:~/unix/two$ strip  libdemo2.so 
zhao@ubuntu:~/unix/two$ nm libdemo2.so 
nm: libdemo2.so: no symbols



你可能感兴趣的:(linux编程学习笔记(四) 编译工具和动态库)