最近,有同事向我多次问及C++关于编译链接方面的问题,包括如下:
1:什么样的函数以及变量可以定义在头文件中
2:extern "C"的作用
3:防止重复包含的宏的作用
4:函数之间是怎么链接起来的
我认为,这些问题不难,书上基本上都有,但要是没有真正思考过,就凭死记硬背,也就是只能"嘴上说说"而已,遇到问题还真棘手,所以我觉得有必要说一下。
C/C++的编译链接过程
其实,"编译"这个词大多数时候,我们指的是由一堆。h,.c,.cpp文件生成链接库或者可执行文件的过程。但是拿C/C++来说,其实这是很模糊的,由一堆C/C++文件生成应用程序包括预处理---编译文件---链接(写的比较粗糙,不影响本文论述)。
首先,要明白什么是编译单元,一个编译单元可以认为是一个。c或者。cpp文件,每一个编译单元首先会经过预处理得到一个临时的编译单元,这里称为tmp.cpp,预处理会把。c或者。cpp直接或者间接包含的其它文件(不只局限于。h文件,只要是#include即可)的内容替换进来,并展开宏调用等。
下面首先看一个例子:
a.h
#ifndef A_H_
#define A_H_
static int a = 1;
void fun();
#endif
a.cpp
[cpp] view plaincopy
#include "a.h"
static void hello_world()
{
}
只有a.h和a.cpp这两个文件,及其简单。首先通过g++的-E参数得到a.cpp预处理之后的内容
coderchen@coderchen:~/c++$ g++ -E a.cpp > tmp.cpp
查看tmp.cpp
# 1 "a.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "a.cpp"
# 1 "a.h" 1
static int a = 1;
void fun();
# 2 "a.cpp" 2
static void hello_world()
{
}
tmp.cpp就是只经过预处理得到的文件,这个文件才是编译器能够真正看到的文件。这个过程就是预处理。
其中#define A_H_的作用是防止重复包含a.h这个头文件,很多人都知道这一点,但是再仔细问,我见过大多数人都说不清楚。
这种宏是为了防止一个编译单元(cpp文件)重复包含同一个头文件。它在预处理阶段起作用,预处理器发现a.cpp内已经定义过A_H_这个宏的话,在a.cpp中再次发现#include "a.h"的时候就不会把a.h的内容替换进a.cpp了。
编译器看到tmp.cpp的时候,会编译成一个obj文件,最后由链接器对这一个对obj文件进行链接,从而得到可执行程序。
编译错误和连接错误
编译错误指的是一个cpp编译单元在编译时发生的错误,这种错误一般都是语法错误,拼写错误,参数不匹配等。
以main.cpp为例(只有一个main函数)
int main()
{
hello_world();
}
编译(加-c参数表示只编译不链接)
coderchen@coderchen:~/c++$ g++ -c -o main.o main.cpp
main.cpp: In function 'int main()':
main.cpp:4: error: 'hello_world' was not declared in this scope
这种错误就是编译,原因是hello_world函数未声明,把void hello_world();这条语句加到main函数前面,再次编译
coderchen@coderchen:~/c++$ g++ -c -o main.o main.cpp
coderchen@coderchen:~/c++$
编译成功,虽然我们调用了hello_world函数,却没有定义这个函数。好,接下来,我们把这个main.o文件链接下,
coderchen@coderchen:~/c++$ g++ -o main main.o
main.o: In function `main':
main.cpp:(。text+0x7): undefined reference to `hello_world()'
collect2: ld returned 1 exit status