杨力祥老师在C++课后给同学留了一道思考题,即探讨C++函数调用时其内存的结构究竟是什么样的。在参考《程序员的自我修养》的过程(一)

2014-11-24 09:08:24 · 作者: · 浏览: 1

本文主要介绍了一下在Linux下开发c/c++时候,不可避免的会开发或者生成.o .a .so这种中间库状态的文件(可能是自己写了一个lib让别人调用,或者提供.c/.cpp文件嵌入别人的Makefile工程)。如何查看这些库文件的一些基本信息。有时候大家编译程序时候(确切的说是链接器链接的时候)很多错误例如"undefine reference",之类的常见错误,原因就是因为没有找到.o .a .so的库文件,导致链接失败。

-------------------------------------------------------------------------------

1、Linux库文件

2、库文件的使用方式

3、利用tar/nm查看库文件的信息

-------------------------------------------------------------------------------

1、库文件的定义之类的就不在此累赘了,有兴趣Google之。说白了就是我们写好一些对应的.h和.c(.cpp)文件,然后通过编译器的编译,生成中间代码供他人使用,他人只需要将你的中间代码include进自己的程序即可。注意,编译器编译成最终可执行的文件需要好几步,基本可以分为:文本解析->语法解析->此法分析->预处理分析->编译->连接。生成中间库是没有链接阶段的,在Linux Gcc下通过-C参数指定只编译不链接,所以如果写了一个.c文件用到了比如pthread_create之类的外部调用,在Gcc -C编译的时候不用-lpthread因为这个时候是不需要链接的。

2、(对库文件熟悉话直接跳过)在各个系统平台上,库文件的格式和形式各不相同,Windows下就是如同xxx.dll或者xxx.lib,*inux下就是xxx.so或者xxx.a。两种分别对应的是静态库和动态库,静态库会连同编译器编译链接进入程序成为程序的一部分,好处是作为程序的一部分不用每次运行时都去load(弊端是可能很多进程都用到这个库但是每个进程中都有一份,动态库的话内存中只有一份,通过重定向来加载),而且不会导致因库的缺失而运行失败,坏处是会导致可执行文件偏大。动态库是程序运行时动态加载到进程里去的,而且可以多进程共,并且方便软件更新,直接替换老的库即可。

到底使用静态还是动态库取决于程序的使用上下文环境,一般第三方库都提供了两种版本,系统库的话一般都是动态链接库,因为同样系统下的库都是一样的。用Linux的Gcc举例:

一、写了一个.h声明一个foo()函数,然后在.c或者.cpp中实现foo()函数

二、gcc(g++) -c -o foo.o foo.c (注意此处不需要.h头文件,头文件只是对库的对外接口描述)

三、生成静态库: ar -r libfoo.a foo.o (静态库.a其实就是.o文件的压缩包,注意这里不支持把.a打入.a)

生成动态库: gcc foo.c -fPIC -shared -o libfoo.so(-fPIC意思是生成位置无关代码,因为动态库是运行时加载的,需要对代码进行重定向,不清楚可以Google一下)

四、写个含有main函数的文件,并调用foo()函数 : gcc(g++) -o test test.c -lfoo,这里-lfoo意思是去找以lib开头的某.so或.a文件,默认优先找.so动态库。 www.2cto.com

需要注意一下的是,如果是cpp引用了c的库或.c那么头文件里要用externc "C"关键字来指定按c的方式读取(根本上是因为c和c++的函数签名不一致,因为c++支持重载,所以按c++的方式是找不到同名的c函数的)。在使用动态库的情况下,程序回去一些预定义的地方找.so文件。比如/usr/lib/下,如果需要自己指定,请修改/etc/ld.so.conf文件。并用ldconfig来刷新cache。

3、如果我们需要查看自己写的库的信息时可以用nm来查看,如查看库中有哪些函数,有哪些全局变量,有哪些依赖别的库的东西等等,下面我们写一个例子来说明一下:

[cpp]
#include

int g1;
int g2 = 0;

static int g3;
static int g4=0;

const int g5=0;

static const int g6 = 0;

int main(int argc, char *argv[])
{
static int st = 0;

int t1;
int t2 = 0;

const int t3 = 0;

printf("printf-function");

return 0;
}

void foo1(){}
static void foo2(){}
[cpp] view plaincopyprint
void overload(int i){}
[cpp] view plaincopyprint
void overload(float i){}

linux的nm命令可以一个文件中的符号列表,列出以上代码Gcc -c编译出的a.o(a.a a.so)可以通过nm命令来查看其中的符号信息:

[cpp]
0000000000000000 t
0000000000000000 d
0000000000000000 b
0000000000000000 r
0000000000000000 r
0000000000000000 n
0000000000000000 n
0000000000000000 B g1
0000000000000004 B g2
0000000000000008 b g3
000000000000000c b g4
000000000000001c r g5
0000000000000020 r g6
U __gxx_personality_v0
0000000000000000 T main
0000000000000000 a nm.cpp
U printf
000000000000003e T _Z4foo1v
0000000000000044 t _Z4foo2v
0000000000000054 T _Z8overloadf
000000000000004a T _Z8overloadi
0000000000000010 b _ZZ4mainE2st
其中左边第一列是符号的地址值,对应源码可以看出递增的规律。第二列是该符号的类型,第三列是符号的名称(比如函数名,变量名):

符号类型:介绍几个最常用的,其他的如果遇到了直接Google:

B --- 全局非初始化数据段(BBS段)的符号,其值表示该符号在bss段中的偏移,如g1

b --- 全局static的符号,如g3

r --- const型只读的变量(readonly)

N --- debug用的符号