C++程序编译之谜(一)——多文件编译的奇怪现象

1、奇怪的现象

在C++多文件编译的时候,很多人都会经常碰见这样的一种情况,在一个cpp文件中调用另一个cpp文件的函数,那么可以直接调用吗?我们来做个试验。

有以下三个文件main.cpp,cal.h,cal.cpp

//main.cpp
#include <stdio.h>
#include "cal.h"
int main(int argc, char *argv[])
{
    printf("add(5,4) is %d\n", add(5, 4));
    return 0;
}

 

//cal.h
#include <stdio.h>
int add(int x, int y);

 

//cal.cpp
#include "cal.h"
int add(int x, int y)
{
    return x + y;
}
那么请问,我如果直接在main.cpp文件中直接调用cal.cpp中的函数方法可以吗?或者我换种方式问,请问可以编译通过吗?可以运行通过输出结果吗?

我在网上搜寻答案的时候,发现有些人表示可以很顺利的运行通过,而有些人表示完全按照提示的方法,复制黏贴,却报了找不到函数方法的错误:

undefined reference to `add(int, int)'

那么请问到底是怎么回事呢?千万不要觉得说肯定是他们漏复制了什么,漏了函数声明什么的,这些都不是原因。那么是什么原因呢?

2、真正的原因

真正的原因是IDE在搞鬼!

首先我们要明白一下,像C/C++、java等高级语言编译生成可执行文件是要经历预处理、编译、汇编、链接这几个过程,像以上三个文件,头文件仅仅只参与到预处理过程,将cal.h的内容包含到另外两个cpp文件中,而另外两个cpp文件是要经历编译、汇编生成机器语言的二进制文件,也就是说两个cpp都会各自生成各自的二进制文件。

但是这个还没完,要生成可执行文件,还要将以上cpp文件生成的二进制文件进行链接起来,并且有个文件中还要提供一个main函数入口,因为C++语言的可执行文件的入口是main函数,不提供的话程序无法运行。

如果在Linux平台上要清楚的展现,就是以下这几个语句:

gcc -c main.cpp  //会生成main.o文件
gcc -c add.cpp   //会生成add.o文件
gcc main.o add.o -o main
生成的.o文件就是二进制文件,然后将二进制文件进行链接生成可执行文件,也可以用以下这个语句一步到位:
gcc add.cpp main.cpp -o main

这样子的话在运行可执行文件,就会输出正确结果,不会报错了。

那么我们再回到开头的那个问题,为什么有些人可以正确的输出不会报错,而有些人却会报错呢?首先我猜测他们都是在window平台执行的,并且是用IDE来编译的,而不同的IDE功能都各不相同,有些IDE非常的智能,它可能会根据在main文件中导入的h文件自动去寻找对应的cpp文件,比如像上面我在main.cpp文件中导入了add.h的头文件,那么IDE会自动去寻找看看是否有add.cpp文件,有的话进行编译、链接,输出结果。

当然,这些只是我的猜测,可能IDE也有其他办法可以找出文件间的关系,然后进行编译。不管如何,有些IDE中确实是可以实现这样子的功能,但有些比较简单的IDE就没有这么智能了,这时候可能就会报以上错误,这个可能就需要你进行某些设置,至于如何设置,这个不同IDE都不相同,大家可以自己去网上搜搜答案。

3、新的疑团

那么可能有些人这时候会提出一个疑问,如果我只想提供add()这个函数的接口,而我不想把函数具体的实现也提供出来,像在网络上如果应用第三方库的时候,别人不希望把他们的源码提供出来,但是又想提供这个接口给调用者调用,那么这时候该怎么做呢?

前面我们提到了如果我们不希望把我们的源码提供出来,但是又想提供这个接口给调用者调用,那么这个该怎么做呢?

我们可以考虑用静态链接库或者动态链接库。

1、链接库

那么什么叫链接库呢?说白了,就是一个二进制文件,是通过特殊方式生成的一个二进制文件,在链接的时候,可以准确地生成可执行文件。

那么静态链接库和动态链接库有什么区别呢?

静态链接库在链接的时候程序会把静态链接库中的所有函数插入到你的可执行文件中,在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。Linux平台一般都是.a结尾的文件,像libadd.a这样,window平台一般是.lib结尾的文件,像add.lib这样。

优点:运行速度比较快
缺点:可执行文件的会比较大

动态链接并没有说把库文件的代码插入到可执行文件中,而是在运行的时候链接库中的函数再加载到可执行文件中,也就是说运行的时候发现调用的函数是在动态库中,那么才去巴拉巴拉的加载。Linux平台一般都是.so结尾的文件,像libadd.so这样,window平台一般是.dll结尾的文件,像add.dll这样。所以很多时候你在window上安装软件,如果你到安装目录下查看,可能会看到很dll结尾的文件,而这些就是第三方的动态库。

优点:可执行文件的会比较小
缺点:运行速度比较慢

那么我们如何生成静态库和动态库,以下生成方法均是在Linux平台。

2、静态链接库

生成静态链接库

首先生成.o文件

gcc -c add.cpp -o add.o

或者直接这样也可以,默认生成的也是.o文件

gcc -c add.cpp

生成静态链接库,静态库命名的规则lib+名字+.a

ar rcs libadd.a add.o

生成可执行文件

gcc main.c libadd.a -o maina

maina是利用静态库生成的可执行文件,这个实际上是比较简单的写法,是头文件、源文件、链接库都在同一目录的情况,如果是在不同的目录的话,就要这样

gcc main.cpp -Iinclude lib/libadd.a -o maina

简化成公式就是以下这样

gcc + 源文件 + -I头文件目录 + lib/libxxx.a + -o 可执行文件名

以上就是利用静态链接库生成可执行文件的方法,比较容易理解。ps:实际上还有另外一种方式生成静态库的,你知道是什么吗?

3、动态链接库

生成动态链接库

首先生成与位置无关的.o文件,-fPIC表示与位置无关

gcc -fPIC -c add.cpp -o libadd.o

ps:这边我也无法这个与位置无关到底是什么个意思,因为我用静态库那边的那个.o文件也可以生成动态库。

然后生成动态库

gcc -shared -o libadd.so libadd.o

组后生成可执行文件

gcc main.cpp libadd.so -o mainso

但是运行可执行文件的时候,可能会报以下错误:

./mainso: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory

这个的意思是找不到动态链接库,解决办法是修改当前用户的.bash_profile文件,添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/yourpath,其中yourpath是你动态库存放的目录,比如我的是/app/process/alittle,最后执行source .bash_profile让环境变量生效。这里要记得一定要另开一个窗口执行,不然还是会这个错误。

以上就是静态库和动态库的内容,涉及到了一些编译过程的命令,所以后面我会和大家说下程序编译的相关过程。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不忘初心t

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值