【c/c++】编译和链接

说明

用惯了图形化的IDE,对编译和链接部分的东西知道的并不多,这里补习一下。

  1. 首先编译和链接是两个东西,虽然在IDE上按一个键结果就出来了,这是因为IDE在后台自己做了不少的事情。
  2. 常见的编译器有gcc/g++,前者是给c用的,后者是给c++用的,两者的编译操作差别不大;常见的连接器有ld。
  3. gcc会把链接的事情也做掉(除非加参数),所以对ld的了解更少......

下面以gcc和ld为例说明c语言(c++也差不多,只是要换编译器为g++)的编译和链接。

首先看下面的一个最简单的例子:

#include <stdio.h>

int main(int argc, const char * argv[]) 
{
    printf("Hello, World!\n");
    return 0;
}

在IDE下,比如这边使用的Xcode,只要Cmd+R,就可以直接得到运行结果了:


虽然不明白IDE打的做了什么,但是觉得很厉害的样子。

对大部分只关注代码实现的人来说,能够正确使用就OK了,但是如果希望进一步了解代码的编译和链接,则需要关掉IDE,打开shell,来探索gcc跟ld了。

gcc

gcc的基本格式如下:

gcc [option] files

先不管参数部分,如下图所示是对前面的main.c代码运行gcc的结果:

可以看到也能得到与Xcode执行时一样的结果(连ld都省了)。到底gcc做了些什么,可以参考Journey of a C Program to Linux Executable in 4 Stages这篇文章。

接下来需要介绍一些常用的gcc选项,上面的例子中,虽然我们没有使用编译选项,但是gcc还是会根据默认的选项值来进行编译。当我们了解了gcc的编译选项,就可以更好的控制编译的过程。

下面是一些常用的选项:

-o file:给输出文件命名,在上例中,我们没有指定名称,所以使用了默认的a.out,如果你指定了名称,比如下面这样,就会有不同的结果:

-Wall:这个是用来显示编译时的告警的。比如在我们的代码中添加一个声明了但未使用的变量:

如果不加-Wall参数,就不会报错。开启-Wall可以让gcc上报一些我们自己没有发现的问题,值得推荐。

-Werror:这个选项的意思是把编译中的warning当作错误。上面的例子中,虽然-Wall之后编译时会有提示,但是最终还是会生成a.out,即整个编译过程是会运行完成的,而如果又加了-Werror,在编译过程中就报错并终止了:

-Wall -Werror一起使用,能够不放过任何的编译问题。

-S:用来生成对应的汇编代码:

查看汇编代码:

可以看到生成的汇编代码是AT&T格式的,但是如果只认识Intel的汇编格式呢?可以再加上-masm=intel这个参数:

这样生成的就是intel格式的汇编代码了:

现在需要用到汇编的机会不多,除非是性能要求需要优化代码,或者debug的时候才会去用吧。

-E:加入了-E选项后,gcc并不进行编译,而是进行预编译,就是把宏进行替换、头文件展开等。在原来的代码上加一个宏,下面是执行-E后的情况:

-c:指定这个选项后,gcc只负责编译,但是不会进行链接:

生成的.o文件需要经过链接才能成为可执行的文件,这里就需要用到ld了,先不介绍。

-save-temps:使用这个命令可以保留在gcc编译和链接过程中的中间文件:

这些中间文件对于了解gcc的工作过程很有帮助。很多文件在之前已经介绍过了,另外的,main.i就是预处理后的代码,main.bc是什么还不清楚...

-l和-L:这两个是用来指定库的,前者指定库名,后者指定库的路径,可以参考编译基础——如何用g++编译自己的库,以及一个简单的makefile文件的说明。

-v:加上这个参数可以在编译时看到不少额外的信息:

-D:后接宏来控制代码的走向。

#include <stdio.h>
#define MAX 255
int main(int argc, const char * argv[]) 
{
#ifdef ERROR
	aaaa;
#endif
	int i = MAX;
	printf("Hello, World!\n");
	return 0;
}

上面的代码中,ERROR宏控制的代码是错的,如果使用-DERROR,则在编译的时候就会包含aaaa这样的代码,导致编译错误:

而不加-D,就不会报错。

这里需要注意下-D和宏之间没有空格。

ld

编译参数介绍到这里,下面需要介绍链接器ld。ld和gcc使用的方式差不多,也是ld +选项 +文件。这里的文件需要是.o文件,即gcc -c编译后的文件。

ld最重要的作用当然是生成可执行的文件。但是还有一个非常重要的作用,就是找到我们的代码中并没有定义的那些函数,然后在当前平台上找到相应的实现。

比如我们的代码中有一个printf,它是c标准库中实现的,所以链接时就需要找到相应的库,并获取到printf的实现代码。

下面是链接的操作过程:

首先,当然要生成.o文件,参见前面讲-c时的图片;

然后,像使用gcc一样,什么参数也不加来执行下:

发现有错误,并没有生成可执行文件。

前两个warning是针对mac的,通过加参数-macosx_version_min就可以解决。

后一个错误是因为没有找到_printf的实现,这是因为我们没有指定库。

下面是修改后的执行命令:

可以看到能够生成a.out了,但是还是有报之前的warning,原因是因为没有指定系统架构,修改成下面的形式:

就可以得到我们需要的结果了。

下面也介绍下ld常用的选项(像-macox_verson_min这种就不说了):

-arch:用来指定系统架构,比如Intel 32位机是i386,Intel 64位机是x86_64。

-lx:对应上面的命令就是-lc,表示的是寻找libc.a或者libc.dylib这样的库,因为printf就在这些库中实现的。

-e:用来指定入口,默认是_main,对应到代码中就是main,所以我们不需要显式得指定,当然指定也没有关系:

从这里我们也可以得出一个设想,即是否可以将main换成其它名字,例如start:

#include <stdio.h>

int start(int argc, const char * argv[]) 
{
	int i;
	printf("Hello, World!\n");
	return 0;
}

下面是编译和链接的命令:

可以看到确实是可行的!虽然并没有多少实际的意义......

以上就是对gcc和ld的介绍,它们可以带的选项还有很多,要了解具体所有选线,可以在shell下使用man gcc/ld来查看。

PS:发现一个问题,在mac中gcc被指向了clang编译器,所以上面的gcc操作实际上都是clang的操作......不过基本的功能都没有变,参数在gcc中也能够正常使用,所以基本上也算是gcc的说明吧......

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值