编译与链接的概
前言:
先说明项目的需求:因为c++有高效的STL库,所以我想用c++编写一个缓存帧图像的JitterBuffer。利用该JitterBuffer缓存的一些措施,来实现帧部分丢失RTP包后对这些丢失的RTP包做重传。这篇文档的小Demo是为上述目的做铺垫的。该demo涉及了Pjsip的C代码编译链接C++代码相关的重要概念。本人选了一个不同的文档风格,来表达我对底层开发的编译链接装载库的理解。直接上代码附带注释,来说明编译库过程中可能遇到与库概念相关的问题。比如最近的对项目pjsip的升级,重新编译,可能会遇到各式各样的编译链接问题。我觉得最重要的理解是:“中间文件的全局符号是暴露给其它中间文件的引用接口”。
文档框架:main.c文件引用JSF_JitterBuffer.cpp文件,而JSF_JitterBuffer.cpp文件又引用了部分C语言的库函数。
/*
JSF_JitterBuffer.cpp文件
*/
#include <iostream>
#include <vector>
#include "JSF_JitterBuffer.h"
int jsf = 9;//强符号,若是 int jsf; 则是弱符号。
//多个同 弱符号 不会产生 .o 链接符号冲突问题
int JitterBuffer(unsigned int count,unsigned char value)
{
unsigned int privCount =count;
printf("c:%d,v:%d\n", count, value);
unsigned char *p = (unsigned char *)malloc(count*sizeof(unsigned char));
memset(p, value, count);
while (count > 0) { --count; printf("%c\n", *p); ++p; }
return privCount*value;
}
//本 JSF_JitterBuffer.cpp 文件 和 JSF_JitterBuffer.h 文件 编译生成一个“独立”的 .o 单元
/*
JSF_JitterBuffer.h文件
*/
#ifndef JSF_JB_H_
#define JSF_JB_H_
//判断本文件是 c 还是 c++ ;
//若是 c 文件,则直接生成 memset 符号,链接时 与 c 库的 memset 符号相连接。
// 若是 c++ 文件,则利用 extern "C" 语法(c++ 专属),不要生成 c++ 类型的 memset 符号,
// 而是生成 c 类型的 memset 符号,链接时 与 c 库的 memset 符号相连接。
#ifdef __cplusplus
extern "C" {
#endif
//c++语言,生成的中间文件的接口名是 C 语言的,可与 c 中间文件相连接
int JitterBuffer(unsigned int ts,unsigned char seq);
//c++语言,生成 c 类型的 memset 符号,可与 c 库的 memset 符号相连接。
void *memset(void *,int,size_t);
void *malloc(size_t size);
int printf(const char *format, ...);
extern int jsf;
#ifdef __cplusplus
}
#endif
#endif
//注意:main.c 中 声明 extern JitterBuffer 与 本文件中的 memset 的区别关系?
/*
main.c文件
*/
#include <stdio.h>
//#include "JSF_JitterBuffer.h"
//本文件如果作了JSF_JitterBuffer.cpp中 定义的符号 的 符号声明 ,就没有必要再包含
//JSF_JitterBuffer.h 头文件
//而 JSF_JitterBuffer.h 头文件中的 #ifndef XXX #define XXX #endif 的
//目的就是为了避免 可能 的重复 定义。
//****现在要转变视角,把编译的一个(x.cpp + include<x.h>)文件看成是一个单独的 x.o 单元,x.cpp中的全局符号,
//是该 x.o 单元暴露给其它 *.o 单元的符号接口,可被 *.o 引用。******//
//链接的时候,去其它的中间文件找该函数的实现。plus:加不加 extern 都能正常链接???
extern int JitterBuffer(unsigned int,unsigned char);//answer:函数或变量可以声明多次,但是只能定义一次!
//int JitterBuffer(unsigned int, unsigned char);
int jsf; //为什么这个没有重复定义???(注意,这里的 jsf 是 “弱符号”),不会与 JSF_JitterBuffer.o 中的jsf冲突
extern int jsf;//这里只是对 jsf 进行声明。
//若是 int jsf = 8; 则链接错误! 与 JSF_JitterBuffer.o 中 定义 的 “强符号” jsf 冲突
//int jsf = 8;
int main()
{
int a = JitterBuffer(8, 102);
//若是 main.o 与 libJB.a(.o 文件集合) 链接成 exe ,则 libJB.a 成为 exe 的一部分。
//若是 main.o 与 libJB.so 链接成 exe ,
//则 exe 仅仅是将 main.o 中的使用的库函数 标记 为一个对 libJB.so 动态链接符号 的引用。
//待到运行装载时,(“动态链接器”) 再把 libJB.so 载入到内存,然后进行对 exe 中的符号进行 符号重定位。
printf("rlt:%d\n", a); //该函数已经在 stdio.h 中声明了,在包含了<stdio.h>后,main.c可以直接使用。
printf("jsf:%d\n", ++jsf);
return 0;
}
//概念:强符号,函数定义 及 用值初始化了的全局变量。
// 弱符号,未初始化的全局变量。
/*
编译shell文本
*/
#!/bin/sh
#几个有用的概念
#在linux系统中;.o文件,.a文件,.so文件,.exe文件均是以 elf 标准格式存储。
####1查看文件name的属性:file name(包含64bit或32bit,x86或arm,文件类型 .o .a .so .exe 或 UTF-8 Unicode text)
####2查看文件name.o的全局符号表:readelf -s name.o(包含链接器对该 .o 扫描得到的可被其它 .o 文件所引用的 全局符号)
####3查看文件name.a中包含的所有中间文件:ar -t name.a(列出.a包含的所有.o文件)
####4查看文件name.a中包含有那些符号段信息:objdump -t name.a(列出包含的所有.o文件的段及符号的详细信息)
####5查看exe或.so所依懒的共享库:ldd exe/.so (列出文件引用符号所依懒的库)
####6查看文件name.so中导出符号:readelf -sD name.so(列出可被应用的全局符号定义,也可以只加 -s 选项)
####7查看exe或.so导入符号(符号定义在其它文件,在运行装载时重定位):readelf -r exe/.so
####8查看exe被执行时动态链接器加载库的打印信息:LD_DEBUG=files exe (=libs,显示查找过程,=bindings,显示符号绑定过程等,详见《程序员的自我修养》page 244)
####查看gcc编译链接的所有库的详细信息:加 -verbose 选项
####判断文件name.so是否是PIC的:readelf -d name.so | grep TEXTREL (无任何输出则为 PIC 的,加 -fPIC 选项)
############## ---dynamic exe (compiler linker with.a library) --- ###############
gcc - c JSF_JitterBuffer.cpp # 实际上 gcc 自动接到 g++ , 加 - c 选项只生成.o 中间文件
ar - rc libJSF_JitterBuffer.a JSF_JitterBuffer.o # 将.o 中间文件 打包 成.a 静态库
# 生成的exe1,注意,虽然链接了.a 静态库(.a 仅仅是.o 文件的集合;其.o 中的内容链接成为 exe1 的一部分;所以说.a 库文件改变后,整个用到的.a 系统都要重新编译过一遍!)。但因为没加 -static 选项,该 exe1 还是运行时加载动态库(例如 libc.so.6)!
gcc - o dynamic_test_exe1 main.c - L. - lJSF_JitterBuffer - lstdc++ #因为是gcc编译,手工指定链接 c++ std 库
############### ---dynamic exe (compiler linker with .so library) --- ###############
gcc - shared - fPIC - o libJSF_JitterBufferSo.so JSF_JitterBuffer.cpp # 若是用上面的 JSF_JitterBuffer.o,则产生.so出错。若用上面的 libJSF_JitterBuffer.a 则可以??(但是,readelf - sD 查看没有对应的被应用全局符号??)。说明? 在语法上可以用.a文件生成.so文件。。。# - shared 指定生成.so 库, - fPIC 指定 生成的.so 库可被多个不同的进程共享(.txt段,一般是 read only 段)
gcc - o dynamic_test_exe2 main.c - L. - lJSF_JitterBufferSo - lstdc++#将 main.o 中使用的库函数 标记 为对libJSF_JitterBufferSo.so库的 动态链接符号 的引用!!!(详见main.c中的说明)(运行时出错,可以把库拷入 / lib)
gcc - o dynamic_test_exe3 main.c - L. - lJSF_JitterBufferSo - lstdc++ - Wl, -rpath ./ #指定运行时所找的路劲
#注意,dynamic_test_exe2 装载运行时,动态链接器,搜索的动态库路径,的搜索先后顺序是:
#1.编译目标代码时指定的动态库搜索路径;(#"-Wl,-rpath 路径", 运行时 ##"-L 路径",编译时 ##"-Wl,-rpath-link 路径",编译时#)
#2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
#3.配置文件 / etc / ld.so.conf中指定的动态库搜索路径;
#4.默认的动态库搜索路径 / lib;
#5.默认的动态库搜索路径 / usr / lib.
#question,为什么dynamic_test_exe1能正常运行,而dynamic_test_exe2报找不
#到error while loading shared libraries: libJSF_JitterBufferSo.so错误?
#因为编译exe1用的JB库是静态库,JB.a已经成为exe1的一部分。而exe2仅仅
#包含JB.so的符号引用。另外exe1中也需要运行时寻找libstdc++.so库,只不过
#它在默认的寻找路径中可以被找到罢了。
############### ----static exe (compiler linker with -static election)----#########################
gcc - o static_test_exe1 main.c - L. - lJSF_JitterBuffer - lstdc++ - static #完全没有运行时动态库的载入,以及运行时有关符号的重定位,exe作为的独立的elf文件被分页映射拷贝到内存。
#gcc - o static_test_exe2 main.c - L. - lJSF_JitterBufferSo - lstdc++ - static #编译出错!!! not found libxxSo
gcc - shared - fPIC - o libJSF_JitterBuffer.so JSF_JitterBuffer.cpp #测试, - static时, - lx 链接 libx.a还是libx.so ?
gcc - o static_test_exe3 main.c - L. - lJSF_JitterBuffer - lstdc++ - static #会成功吗?ok,会自动链接静态版本库 libx.a
#由上得出,单纯由.so 不能制作.a 库 ?
#但.a 库可以生成.so 库吗 ?
#另外.so 库能生成 另一个.so 库吗?