基础知识复习一下顺便扫盲,一直没好好听课,看看基础
这里有一篇介绍GCC的文章https://blog.csdn.net/itianyi/article/details/43054199;
编译过程详解https://www.cnblogs.com/CarpenterLee/p/5994681.html;
https://blog.csdn.net/dylandong/article/details/60465718;
C语言的编译过程到底是怎样的
1.预处理(Preprocessing), 2.编译(Compilation), 3.汇编(Assemble), 4.链接(Linking)。
这里有一个例子,假设我们自己定义了一个头文件mymath.h
,实现一些自己的数学函数,并把具体实现放在mymath.c
当中。然后写一个test.c
程序使用这些函数。程序目录结构如下:
├── test.c
└── inc
├── mymath.h
└── mymath.c
程序代码:
// test.c
#include <stdio.h>
#include "mymath.h"// 自定义头文件
int main(){
int a = 2;
int b = 3;
int sum = add(a, b);
printf("a=%d, b=%d, a+b=%d\n", a, b, sum);
}
头文件定义:
// mymath.h
#ifndef MYMATH_H
#define MYMATH_H
int add(int a, int b);
int sum(int a, int b);
#endif
实现“”
// mymath.c
int add(int a, int b){
return a+b;
}
int sub(int a, int b){
return a-b;
}
1.预处理(Preprocessing)
预处理用于将所有的#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。gcc
的预处理是预处理器cpp
来完成的,你可以通过如下命令对test.c
进行预处理:
gcc -E -I./inc test.c -o test.i
或者直接调用cpp
命令
$ cpp test.c -I./inc -o test.i
上述命令中-E
是让编译器在预处理之后就退出,不进行后续编译过程;-I
指定头文件目录,这里指定的是我们自定义的头文件目录;-o
指定输出文件名。
经过预处理之后代码体积会大很多:
预处理之后的程序还是文本,可以用文本编辑器打开。
2.编译(Compilation)
这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。编译的指定如下:
$ gcc -S -I./inc test.c -o test.s
上述命令中-S
让编译器在编译之后停止,不进行后续过程。编译过程完成后,将生成程序的汇编代码test.s
,这也是文本文件,内容如下:
// test.c汇编之后的结果test.s
.file "test.c"
.section .rodata
.LC0:
.string "a=%d, b=%d, a+b=%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $2, 20(%esp)
movl $3, 24(%esp)
movl 24(%esp), %eax
movl %eax, 4(%esp)
movl 20(%esp), %eax
movl %eax, (%esp)
call add
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl %eax, 12(%esp)
movl 24(%esp), %eax
movl %eax, 8(%esp)
movl 20(%esp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
3.汇编(Assemble)
汇编过程将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件,是二进制格式。gcc
汇编过程通过as
命令完成:
$ as test.s -o test.o
等价于:
gcc -c test.s -o test.o
这一步会为每一个源文件产生一个目标文件。因此mymath.c
也需要产生一个mymath.o
文件
4.链接(Linking)
链接过程将多个目标文以及所需的库文件(.so等)链接成最终的可执行文件(executable file)。
命令大致如下:
$ ld -o test.out test.o inc/mymath.o ...libraries...
上述过程是以Linux下C语言的编译过程为例,使用GCC编译器完成的。hello.c(源程序)是一条高级C语言程序,但是,它却无法直接驱动硬件CPU直接执行。为了我们编写的hello.c程序可以被执行,驱动硬件电路工作,hello.c程序必须经过一些列处理步骤,将源程序转化为可执行性的目标程序。
机器语言就是处理器可以直接理解(与生俱来就能理解)的编程语言,机器语言有时也被叫做原生代码(Native Code)[1],而机器语言是一种以“0、1、0、1”的表现形式二进制代码,因此源程序的编译过程任务即就是:将以高级语言编写的程序转换为以二进制代码的可执行性目标程序。
那么什么是编译器?
编译 [1] 是从源代码(通常为高级语言)到能直接被计算机或虚拟机执行的目标代码(通常为低级语言或机器语言)的翻译过程。然而,也存在从低级语言到高级语言的编译器,这类编译器中用来从由高级语言生成的低级语言代码重新生成高级语言代码的又被叫做反编译器。也有从一种高级语言生成另一种高级语言的编译器,或者生成一种需要进一步处理的的中间代码的编译器(又叫级联)。
典型的编译器输出是由包含入口点的名字和地址, 以及外部调用(到不在这个目标文件中的函数调用)的机器代码所组成的目标文件。一组目标文件,不必是同一编译器产生,但使用的编译器必需采用同样的输出格式,可以链接在一起并生成可以由用户直接执行的EXE,
编译器可以生成用来在与编译器本身所在的计算机和操作系统(平台)相同的环境下运行的目标代码,这种编译器又叫做“本地”编译器。另外,编译器也可以生成用来在其它平台上运行的目标代码,这种编译器又叫做交叉编译器。交叉编译器在生成新的硬件平台时非常有用。“源码到源码编译器”是指用一种高级语言作为输入,输出也是高级语言的编译器。例如: 自动并行化编译器经常采用一种高级语言作为输入,转换其中的代码,并用并行代码注释对它进行注释(如OpenMP)或者用语言构造进行注释(如FORTRAN的DOALL指令)。
这里有一个概念的区分:
编译器和编辑器的区别
编译器是把java源代码编译成二进制字节码的程序,编辑器是让我们编写java源代码更方便快捷的工具。
Java中的编译器一般指的是javac.exe,编辑器指的是Eclipse、Idea、NetBeans等。
同理.net中的编译器是csc.exe,编辑器是Visual Studio。C语言中的编译器是cl.exe或者gcc.exe,编辑器是Visual Studio、Dev C++等。
编译器与编辑器详解:https://blog.csdn.net/u014492609/article/details/42393637;
http://blog.51cto.com/elephantliu/559415;
上面的连接里又会说到SDK(Software Development Kit,软件开发工具包)和IDE(Integrated Development,集成开发环境)的区别,当然这是题外话。