前言
我想写一个系列的博客去解释编译,链接的过程。作为自己工作一年的总结。让大家不要困惑在配环境这种问题上。一、编译
为什么会有编译这个过程呢?其实本质的原因是:CPU是无法直接理解我们写的程序语言(像C,C++等),有过一点汇编语言基础的我们会了解到CPU执行的是一系列指令集,像mov,jmp,call等。当然不同的CPU执行的指令集还存在差异,这些指令集是由像英特尔的开发人员设计的。所以我们写的高级程序语言是需要通过编译这个过程,转化成CPU能理解的指令集。这就是编译原理的目标。当然这个过程涉及到的技术含量是非常大的。(听说最近《编译原理》的作者拿了图灵奖)我们可以使用GCC实现编译的过程。其实大家刚开始的时候都会用IDE(集成开发环境),像微软的vs。装过vs的电脑上一定有cl.exe这个程序的。大家可以尝试用搜索工具找一下。
当然我们不应该把学习的重点放在纠结用GCC,还是用cl.exe。我们重点应该去理解学习编译的这个过程。我们可以在Ubuntu上用GCC去编译一个我们熟悉的程序hello world.
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
我们使用GCC去编译它,(注意只是编译)
gcc -Wall -c main.c
个人总结gcc是一个命令行程序,我们可以通过添加一些参数去控制它的执行。如"-Wall"是开启警告信息,这样你在编译链接的过程中如果出现错误,gcc会打印出来的。“-c”的意思是,编译,不去链接。我们可以通过GCC手册去学习这些以及别的参数的含义。
执行完这个命令后我们会发现多了一个main.o的文件。
我们可以尝试使用文本的方式去查看这个文件,比方说使用cat命令。
我们会发现很多乱码,这是因为经过gcc -c编译后的程序已经是二进制的形式了,我们不能用普通文本的方式去查看。这个时候我们可以用objdump这个工具去查看。
我们可以清晰的看到编译后的汇编指令。为什么objdump就可以看到呢?是因为objdump知道main.o的文件格式。main.o是有确定的文件格式的,可以理解为一个结构体。这种文件格式的名字叫做ELF。后边有时间会展开去讲,去解析这种文件格式。
需要用到各种各样的头文件,面对这样的需求,我们要怎么处理呢?
二、编译多个文件
假设我们有一下三个文件
main.c
#include "hello.h"
int main()
{
hello("world");
return 0;
}
hello.h
#ifndef HELLO
#define HELLO
void hello(const char * name);
#endif
hello_fn.c
#include <stdio.h>
#include "hello.h"
void hello(const char * name)
{
printf("Hello, %s! \n", name);
}
我们可以在命令后面添加多个文件,同时完成编译。
三.关于头文件
在上面的例子中我们用到两个类型的头文件,一种系统的,一个自定义的。
gcc是怎么找到stdio.h和hello.h这两个头文件的呢?其实关于头文件gcc是有一个搜索路径的,他可以在当前文件夹下搜索,也可以在系统指定的文件夹下搜索。stdio.h是在系统文件夹下,hello.h是在当前文件夹下。
其实这里关于include<>和include""的区别就是,默认尖括号是在系统文件夹下的,双引号是在其他文件夹下的。
如果我们用的头文件不在系统指定目录和当前目录,那该怎么办呢?
像这种情况。我们把hello.h放在include文件夹下。
这个时候编译会怎么样呢?
注意这里是error。怎么才能找到include文件夹下的头文件呢?我们需要加上-I参数。
gcc -Wall -c -I ./include/ main.c
这里说一下大家经常使用的vs这个IDE。虽然是集成开发环境,但是在搭建一些大的项目的时候还是需要自己手动去添加一下东西的。像opencv.
如果我们没有添加头文件会出现找不到头文件的情况。
E1696 无法打开 源 文件 "opencv2/opencv.hpp"
C1083 无法打开包括文件: “opencv2/opencv.hpp”: No such file or directory
怎么让vs去找到这些头文件呢?
其实道理和作用是一样的。
三.关于gcc的搜索路径
我们可以通过-v这个参数去观察gcc的搜索路径。
我们可以看到gcc对头文件的搜索路径如下。
四.关于预处理
因为上边写了好多关于头文件的事,所以我想讲一下预处理。
我们可以把#include<FILE.h>看成是一种预处理时候的指令。当我们遇到这个指令后,会把FILEf.h这个文件展开,仅此而已。
为了证明这个我们运行
gcc -Wall -c -E -I ./include/ main.c
就可以在终端打印出经过预处理的文件,没有#define, #include, 这样的指令。注意#的作用是注释的意思,是方便我们调试程序。hello.h中的代码已经被复制到了新的文件中。
下面这段话是解释这些注释的含义的。
# 1 "./include/hello.h" 1
简单解释一下,上面语句的含义:
第一行的代码是来自./include/hello.h