g++编译过程和动态链接库

简介

gcc :GNU编译器套件(GNU Compiler Collection)是到了4.7,才真正支持c11的。
gcc & g++现在是gnu中最主要和最流行的c & c++编译器 。
g++是将默认语言设为c++,链接时自动使用C++标准库而不用c标准库 。
C++标准库:http://www.runoob.com/cplusplus/cpp-standard-library.html

一、编译过程

  1. 预编译过程:处理宏定义和include,去除注释,不会对语法进行检查,生成.i文件:命令:g++ -E Test.cpp > Test.i
  2. 编译过程:检查语法,生成汇编指令.s文件;[编译器egcs];命令:g++ -S Test.cpp -o Test.s
  3. 汇编过程:汇编变为目标代码(机器代码)生成.o的文件 [汇编器as];命令:g++ -c Test.cpp
  4. 链接过程:生成可执行程序[链接器ld];命令:g++ Test.o -L F:\vs2008\VC\include\iostream

二、编译器参数

  • -std=c++11 指定语言标准,只有编译C或C++时才有用,除了c++11,还有c90、c89、c99等等

调试选项 -g和-pg

  • -g :生成能被GNU调试器使用的调试信息;用level指出需要多少信息,默认的level值是2
  • -pg:在编译好的程序里加入额外的代码。运行程序时, 产生gprof用的剖析信息以显示你的程序的耗时情况。

优化选项 -O(0~3) 优化编译、链接,-O0表示没有优化,-O1为缺省值,-O3优化级别最高;

  • -O:进行跳转和延迟退栈两种优化
  • -O2:除了完成-O1,还进行额外的调整工作,如指令调整??
  • -O3:包括循环展开和其他一些与处理特性相关的优化工作??
  • 但是通常情况下,自动的东西都不是太聪明,太大的优化级别可能会使生成的文件产生一系列的bug。一般可选择2;3会有一定风险。

目录选项

  • -I:用来指定头文件目录;-Idirname:将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数;/usr/include目录一般是不用指定的,如果不加你会得到一个"xxxx.h: No such file or directory"的错误,gcc xxx.c -lm(动态数学库,库名m) -lpthread(链接多线程库) -ldl(链接dl库)
    库文件放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接,而放在其他目录,则要使用-L紧跟库文件所在目录;#include< > 引用的是编译器的类库路径里面的头文件。 #include" " 引用的是你程序目录的相对路径中的头文件。-l和-L:指定程序要链接的库,紧接库名;

错误与警告选项

  • -v:列出所有编译步骤
  • -w:关闭所有警告信息
  • -Wall:打印出gcc提供的警告信息
  • -Wextra:打印出更多的警告信息,比开启 -Wall 打印的还多
  • -Werror:要求GCC将所有的警告当成错误进行处理,在警告发生时中止编译过程。
  • -Wno-sign-compare: 关闭当有符号转换为无符号时,有符号和无符号值比较产生的错误警告。
  • -Wno-unused-local-typedefs:忽略本地未使用的类型定义警告。
  • -Wno-deprecated-declarations:关闭使用废弃API的警告.

链接方式

  • -static:禁止使用动态库。优点:程序运行不依赖于其他库。缺点:可执行文件比较大。
  • -shared:此选项将尽量使用动态库,为默认选项。优点:生成文件比较小。缺点:运行时需要系统提供动态库。 需要配合参数-fPIC使用
  • -symbolic :建立共享目标文件的时候,把引用绑定到全局符号上。对所有无法解析的引用作出警告(除非用连接选项,'-Xlinker -z -Xlinker defs'取代)。只有部分系统支持该选项。
  • -Wl,-Bstatic:告诉链接器ld只链接静态库,如果只存在动态链接库,则链接器报错。
  • -Wl,-Bdynamic:告诉链接器ld优先使用动态链接库,如果只存在静态链接库,则使用静态链接库。
  • -m32为生成32位的动态链接库。 -m64位生成64位的动态链接库。

其他选项

  • -fpic:编译器就生成位置无关目标码.适用于共享库(shared library)
  • -fPIC:编译器就输出位置无关目标码.适用于动态连接(dynamic linking)。
  • -pipe:使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题
  • -msse:让编译器使用cpu的sse指令集,可以使用mmx寄存器计算单精度浮点运算。
  • -msse2:让编译器使用奔腾cpu的指令集,可以使用mmx寄存器计算双精度浮点运算。
  • -mno-mmx-mno-sse-mno-sse2:不使用MMX,SSE,SSE2指令。
  • -fpermissive:把代码的语法错误作为警告,并继续编译。请谨慎使用该选项。
  • -rdynamic:用来通知链接器将所有符号添加到动态符号表中,程序动态调用动态库中的函数,编译时用到该选项,通常和-ldl一起用。
  • -D name=def: 加入宏定义,若不指定def,则默认为1
  • //一些进行缓冲区溢出实验时可能需要的选项
  • -fstack-protector-fno-stack-protector:是否开启堆栈保护,这里的保护是在返回地址之前加入一个验证值来确保返回地址不被破坏
  • -z execstack :启用可执行栈,默认是禁用的

三、生成动态链接库

在实际开发过程中,各个模块之间会涉及到一些通用的功能,比如读写文件,查找、排序。为了减少代码的冗余,提高代码的质量,可以将这些通用的部分提取出来,做出公共的模块库。通过动态链接库可以实现多个模块之间共享公共的函数。

创建动态库,通过shared和fPIC编译参数生产so动态链接库文件。程序在调用库函数时,只需要连接上这个库即可。

/*caculate.h*/
#ifndef CACULATE_HEAD
#define CACULATE_HEAD
int add(int a, int b);
int sub(int a, int b);
int div(int a, int b);
int mul(int a, int b);
#endif

/*caculate.c文件*/
#include "caculate.h"
extern "C"{
int add(int a, int b)
{ 
  return (a + b);
}
int sub(int a, int b)
{
  return (a - b);
}
int div(int a, int b)
{ 
  return (int)(a / b);
} 
int mul(int a, int b)
{ 
  return (a * b); 
}
}


编译生产libcac.so文件如下

g++ -fPIC -shared -o libxxx.so xx1.cpp xx2.cpp xx3.cpp  //多个文件成成一个动态连接库

g++ -fPIC -shared caculate.cpp -o libcac.so


四、调用动态库

#include <stdio.h>
#include "caculate.h"
int main(){ 
  int a = 20; 
  int b = 10; 
  printf("%d + %d = %d\n", a, b, add(a, b));
  printf("%d - %d = %d\n", a, b, sub(a, b));
  printf("%d / %d = %d\n", a, b, div(a, b));
  printf("%d * %d = %d\n", a, b, mul(a, b)); 
  return 0;
}


编译生产可执行文件main如下: g++ main.c -o main -L./ -lcac

五、动态获取动态链接库的函数

linux提供dlopen、dlsym、dlerror和dlcolose函数获取动态链接库的函数。
通过这个四个函数可以实现一个插件程序,方便程序的扩展和维护。

#include <dlfcn.h> 以指定模式打开指定的动态链接库文件,并返回一个句柄给 dlsym()的调用进程

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);//使用dlclose()来卸载打开的库。 Link with -ldl.

  • dlopen 该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。

flag在linux 下有三种解析方式 RTLD_LAZY 暂缓决定,等有需要时再解出符号  RTLD_NOW 立即决定,返回前解除所有未决定的符号。

作用范围,可与解析方式通过“|”组合使用。
RTLD_GLOBAL:动态库中定义的符号可被其后打开的其它库解析。
RTLD_LOCAL: 与RTLD_GLOBAL作用相反,动态库中定义的符号不能被其后打开的其它库重定位。如果没有指明是RTLD_GLOBAL还是RTLD_LOCAL,则缺省为RTLD_LOCAL。

handle = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);

  • dlsym 根据动态链接库操作句柄与符号,返回符号对应的地址,不但可以获取函数地址,也可以获取变量地址。

void _dlsym(void_handle,const char*symbol)

handle:由dlopen打开动态链接库后返回的指针;

symbol:要求获取的函数或全局变量的名称。

  • dlclose函数负责关闭指定句柄的动态库,当该库的使用计数为0时,就会被卸载
#include <stdio.h> 
#include <dlfcn.h>

#define DLL_FILE_NAME "libcac.so" 
typedef int (CAC_FUNC)(int, int); 
//using function = void()(int, int);
//c++11 
int main() { 
void *handle; 
CAC_FUNC func = NULL; 
char *error; 
int a = 30; 
int b = 5;
handle = dlopen(DLL_FILE_NAME, RTLD_NOW);
if(handle == NULL)
{
    fprintf(stderr, "Failed to open libaray %s error:%s\n", DLL_FILE_NAME, dlerror());
    return -1;
}
*(void **) (&func) = dlsym(handle, "add");
if(func == NULL){
    printf("ERROR:%s:dlsym\n", dlerror());
    return -1;
}
printf("%d + %d = %d\n", a, b, func(a, b));

func = (CAC_FUNC)dlsym(handle, "sub");
printf("%d + %d = %d\n", a, b, func(a, b));

func= (CAC_FUNC)dlsym(handle, "div");
printf("%d + %d = %d\n", a, b, func(a, b));

func = (CAC_FUNC)dlsym(handle, "mul");
printf("%d + %d = %d\n", a, b, func(a, b));

dlclose(handle);
return 0;
}


输出:g++ maindl.cpp -o maindl -ldl

30 + 5 = 35 30 + 5 = 25 30 + 5 = 6 30 + 5 = 150

void *dlsym(void _handle, const char _symbol); 返回值为void (void )&(func)是将函数指针的地址强制转换void类型,然后使用取值,获取dlsym的返回值 实际这个地方没有必要这样,函数指针本来就是地址,可以直接用 func = dlsym(handle, "add");

  • void 和 void*

1、函数的返回值如果是 void 类型的,则表示这个函数的执行结果是没有返回值的;
2、函数的返回值如果是 void* 类型的,则表示返回一个内存地址,这个内存空间存放的数据类型是void类型的,即无类型的,也可以说是万能型的
3、在实际开发中,void* 使用时最终都要强制转换成某种明确的数据类型

 

转载于:https://my.oschina.net/u/347414/blog/1858115

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值