由“c++链接错误:未定义的引用“引发的思考

背景描述

日常的项目开发构建中, 常常出现 未定义的引用的错误。 出现这种错误的原因有很多, 此文描述c++引用c函数导致的问题。

示例程序如下:

// add.c
int add(int a, int b)
{
    return a + b;
}
// test.c
int add(int , int);
int main()
{
    int a = 1;
    int b = 1;
    int c = add(a, b);

    return 0;
}

编译策略如下:

  • gcc 编译add.c 和 test.c
  • g++ 编译add.c 和 test.c
  • gcc 编译add.c , g++编译test.c

gcc 编译add.c 和 test.c

编译过程如下:

[test_user]$ ls
add.c  test.c
[test_user]$ gcc -c add.c test.c
[test_user]$ ls
add.c  add.o  test.c  test.o
[test_user]$ gcc add.o test.o  -o main_c
[test_user]$ ./main_c
[test_user]$

程序可以正确编译和运行。

g++ 编译add.c 和 test.c

[test_user]$ ls
add.c  test.c
[test_user]$ g++ -c add.c test.c
[test_user]$ ls
add.c  add.o  test.c  test.o
[test_user]$ g++ add.o test.o  -o main_cpp
[test_user]$ ls
add.c  add.o  main_cpp  test.c  test.o
[test_user]$ ./main_cpp
[test_user]$

程序可以正确编译和运行。

gcc 编译add.c , g++编译test.c

[test_user]$ ls
add.c  test.c
[test_user]$ gcc -c add.c -o add.o
[test_user]$ g++ -c test.c  -o test.o
[test_user]$ ls
add.c  add.o  test.c  test.o
[test_user]$ g++ add.o test.o -o main_cpp
test.o:在函数‘main’中:
test.c:(.text+0x21):对‘add(int, int)’未定义的引用
collect2: 错误:ld 返回 1
[test_user]$

连接时报错了。 原因时 test.c中没有找到add的定义, add不是在add.c中定义了么? 为什么会找不到呢 ?

其实 c编译后的符号与cpp编译后的符号是不同的, gcc编译 后的符号就是函数本身, 而g++编译后的符号是加了修饰的, 举例如下:

[test_user]$ gcc -c add.c -o add_c.o
[test_user]$ g++ -c add.c -o add_cpp.o
[test_user]$ nm add_c.o
0000000000000000 T add
[test_user]$ nm add_cpp.o
0000000000000000 T _Z3addii
[test_user]$

可以看到, gcc编译add.c后add.o中的符号就是add,而g++编译test.c后test.o中的符号是_Z3addii,所以链接时test.o找_Z3addii肯定是找不到的。

这种问题如何解决呢 ? 难道必须都使用gcc编译,或者都使用g++编译么 ? 肯定不是, 这就需要c++中的extern "C"上场了。

extern “C”

C++中使用extern “C” 声明或定义的内容以C语言的编译规则进行编译。

[test_user]$ cat test.c
int add(int , int);

int main()
{
    int a = 1;
    int b = 1;
    int c = add(a, b);
    return 0;
}
[test_user]$ g++ -c test.c  -o test.o
[test_user]$ nm test.o
0000000000000000 T main
                 U _Z3addii
[test_user]$ vim test.c
[test_user]$ cat test.c
extern "C" int add(int , int);

int main()
{
    int a = 1;
    int b = 1;
    int c = add(a, b);
    return 0;
}
[test_user]$ g++ -c test.c  -o test.o
[test_user]$ nm test.o
                 U add
0000000000000000 T main
[test_user]$

可以看到, 加了extern "C"修改的add, 使用g++编译后符号位add; 而未加 extern "C"修改的add, 使用g++编译后符号为 _Z3addii。

加了 extern "C"修改的add, 也可以与gcc编译的add.c一起链接, 如下:

[test_user]$ g++ -c test.c  -o test.o
[test_user]$ nm test.o
                 U add
0000000000000000 T main
[test_user]$ gcc -c add.c -o add.o
[test_user]$ g++ add.o test.o -o test
[test_user]$ ./test
[test_user]$

extern "C"是C++才有的, C语言中并没有。 所以 test.c无法用gcc编译。此时就需要编译宏 __cplusplus 上场了。

编译宏 __cplusplus

__cplusplus 只在g++编译时被定义, gcc编译是未定义的, 所以可以通过此宏判断是g++编译还是gcc编译, 只有g++编译才能加extern “C”

[test_user]$ cat add.c
int add(int a, int b)
{
    return a + b;
}
[test_user]$ cat test.c
#ifdef __cplusplus
extern "C" int add(int , int);
#else
int add(int , int);
#endif

int main()
{
    int a = 1;
    int b = 1;
    int c = add(a, b);
    return 0;
}
[test_user]$ gcc -c add.c -o add.o
[test_user]$ ls
add.c  add.o  test.c
[test_user]$ gcc add.o test.c
[test_user]$ ls
add.c  add.o  a.out  test.c
[test_user]$ rm a.out
[test_user]$ ls
add.c  add.o  test.c
[test_user]$ g++ add.o test.c
[test_user]$ ls
add.c  add.o  a.out  test.c
[test_user]$ ./a.out
[test_user]$

可以看到, gcc编译后的add.o, 既可以使用gcc与test.c链接, 也可以使用g++与test.c链接。

有的同学会问, 统一使用gcc或者g++编译不就好了, 为什么要做这种兼容?

原因是: 很多项目中静态库或动态库是C语言编写的, 而需要在C++程序中使用这些库函数, 此时就必须做兼容, 否则会出现“未定义的引用”错误。

你可能感兴趣的:(编译,linux,c++,c语言,开发语言)