undefined reference问题排查

1缘由

         最近开发中遇到一个“undefinedreference”的连接问题,花了半小时时间排查。决定将这个问题解决思路捋顺一下。

         ”undefinedreference“问题发生在连接阶段,连接阶段无法找到定义。解决这几个问题一般思路:

a.      链接时缺失了相关目标文件.o。

b.      链接时缺少相关的库文件(.a/.so)。

c.      链接的库文件中又使用了另一个库文件。

d.      多个库文件链接顺序问题。(依赖其他库的库一定要放到被依赖库的前面)

e.      在c++代码中链接c语言的库。(gcc和g++编译方式存在差别)。

f.       使用工具nm、ldd去查看相关信息。

2问题情境

         gcc编译一个可执行文件app,连接一个g++编译的.so库。报错找不到相应的函数。

3问题原因

         函数参数packet在不同的模块定义了两个,用g++编译后,函数会编译为

000605ce T_Z11GetTermTypePK7_Packet,函数名加参数,因C++函数重载机制,导致函数编译后的符号带有packet,有因为程序多个模块定义了不同的packet,当你头文件用错后,导致在.c中gcc编译时,无法找到对象的定义。

4静态库和动态库

         编译器编译成最终可执行的文件需要好几步,基本可以分为:文本解析->语法解析->此法分析->预处理分析->编译->连接。

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

4.1静态库和动态库的生成

         看如下代码,main.c,a.cpp,a.h,b.cpp,b.h

b.h:

#ifndef __B_H__

#define __B_H__

 

extern int print();

extern int print(int b);

 

#endif

b.cpp:

#include <stdio.h>

#include "b.h"

 

int print()

{

    printf("b\n");

    return 0;

}

 

int print(int b)

{

    printf("%d\n", b);

    return 0;

}

a.h:

#ifndef __A_H__

#define __A_H__

 

extern int display();

 

#endif

a.cpp:

#include <stdio.h>

 

#include "a.h"

#include "b.h"

 

int display()

{

    print();

}

Main.cpp:

         #include <stdio.h>

 

#include "a.h"

 

int main()

{

    display();

    return 0;

}

生成动态库liba.so:

         g++a.cpp b.cpp -fPIC -shared -o liba.so

然后编译main.cpp:

         gcc-o main main.c liba.so

         报错:main.c:(.text+0x7):undefined reference to `display'

         原因:c中调用.cpp中的函数,找不到对应的函数符号,因为gcc和g++编译成的函数描述符不同。

         g++编译:00000540 T_Z7displayv

         gcc编译:000003e0 Tdisplay

         以上很容易看出,display函数的描述符不同,所以找不到display。

         解决办法,C中调用c++中的函数时,让其按C的函数解释方式执行。在c++头文件中添加如下模块:

#ifndef __A_H__

#define __A_H__

 

 

#ifdef __cplusplus

extern "C"{

#endif

 

int display();

 

#ifdef __cplusplus

}

#endif

 

#endif

__cplusplus为使用c++编译选项的宏。

         这样display函数不能重载。

4.2C/C++编写的程序互调

         在c/c++语言混合编程中,活做接口程序时,需考虑的问题,下面以举例的形式来说明。

4.2.1在C++中调用C库的例子:
1做一个C动态库:

// hello.c:

#include <stdio.h>

void hello()
{
  printf("hello\n");
}

编译并copy到系统库目录下(也可以自己定义库目录,man ldconfig):
[root@coredump test]# gcc --shared -o libhello.so hello.c
[root@coredump test]# cp libhello.so /lib/
2).写个C++程序去调用它:

// test.cpp

#include <iostream>

#ifdef __cplusplus
extern "C" {               // 告诉编译器下列代码要以C链接约定的模式进行链接
#endif

void hello();

#ifdef __cplusplus
}
#endif

int main()
{
  hello();

  
return 0;
}


编译并运行:
[root@coredump test]# g++ test.cpp -o test -lhello
[root@coredump test]# ./test
hello

4.2.2C调用C++库:
    上面那个例子已经说明。

5常用协助工具

         1、nm [options]file    列出file中的所有符号,有助于调试函数符号。

   [option]

    -c   将符号转化为用户级的名字

    -s   当用于.a文件即静态库时,输出把符号名映射到定义该符号的模块或成员名的索引

    -u   显示在file外定义的符号或没有定义的符号

    -l   显示每个符号的行号,或为定义符号的重定义项

2、ar {dmpqrtx}[member] archive file    用于操作高度结构化的存档文件(.a)

   [options]

   -c    创建存档文件

   -s    创建或升级从符号到定义他们的成员之间的交叉索引映射表

   -r    替换archive中的同名文件或添加新文件

   -q    不检查而直接添加文件到存档文件的末尾

ranlib [-v|-V] file 的作用跟ar -sfile相同

3、ldd[options] file    列出file运行所需的共享库,以及常引用路径,如果不存在会报not found

   [options]

   -d    执行重定位并报告所有丢失的函数

   -r    执行对函数和对象的重定位并报告丢失的任何函数或对象

4、 ldconfig [options][libs]    决定位于目录/usr/lib和/lib下的共享库所需的运行的链接,这些链接由[libs]指定并被保存到/etc/ld.so.conf中

   [options]

   -p    打印文件/etc/ld.so.conf的内容

   -v    更新/etc/ld.so.conf

5、 ld.so    动态链接/加载器

   ld.so使用的两个环境变量

   $LD_LIBRARY_PATH 告诉ld.so去哪里查找保存在非标准目录下的共享库,冒号分隔,对应文件/etc/ld.so.conf

   $LD_PRELOAD告诉ld.so用户指定的在所有库加载之前加载的库所在的目录,空格分隔,对应文件/etc/ld.so.preload

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值