我们写的代码实际上是一个文本文件,对文本文件运行不会生成可执行程序。但是在VS或者gcc等开发环境中,内置了编译器和链接器,使它生成一个计算机可以理解可以执行的二进制程序,这个可执行程序用.exe为后缀。
编译和链接主要就是解释代码是如何从文本文件变成可执行文件的。
编译这个过程就是把一个工程中的各源文件,通过编译器,编译成.obj为后缀的目标文件。
链接就是把这一个个的.obj为后缀的目标文件整合处理成一个.exe为后缀的可执行程序。
需要注意,.obj为后缀的文件也是计算机可以理解的二进制文件。
编译的内容大致可以分为三部分:预处理、编译、汇编
预处理阶段主要处理以下内容。
1.头文件的包含:如#include? <stdio.h>? , 如#include? "add.h"。
2.宏定义:如 #define MAX? 5
3.删除注释? 注释的内容不参与后面的编译和任务,在预处理阶段就会被删除,它只是给程序员看的。
预处理过后,.c的程序文件会生成一个.i为后缀的文件,这个文件可以看到在预处理后,我们的代码是怎样的。
但是在VS的集成开发环境中,这种.i为后缀的文件在生成之后就被删掉了,不会被保留辖来。我们难以在当前目录底下看到程序在这个阶段的细节。所以可以通过Linux在gcc环境下,通过一些指令来看到.i文件的代码。
头文件的包含是这样的。如果是<>的包含,那么会先从库文件中查找,在预处理后,就会把所包含的库文件内容,替换掉#include <stdio.h>这句代码(以它为例)。如果是" "的包含,那么会先从当前目录下查找所包含的文件名字,如果没找到,再从库目录下面找,找到了就把该文件的内容替换掉#include "add.h" 的代码。相当于把它们的内容展开了。
有时候包含的文件比较大,重复包含会占用很多空间。因此可以选择条件编译(见下文编译部分),也可以选择#pragma once 来避免头文件的重复引用。
常见的有:
例:
#define FLOWER(n) ((n)+(n))
int main()
{
int a = 3;
a = FLOWER(a);
printf("%d\n",a);
return 0;
}
这个例子是把一个数变成它的两倍。其他的还可以有乘法除法等等。
需要注意的是:
宏定义是没有类型检验的,所以无论传什么类型都可以,只要它能够执行。
宏是直接替换的,它可能会与外面的式子发生一些优先级运算的问题。所以要保证宏定义的运算结果是一个整体,最好把它们都加上括号。
宏一般是每个字母都大写,与函数区分。命名约定。
#define MALLOC(type,num) ((type*)malloc(sizeof(type)*num))
int main()
{
int *p = NULL;
p = MALLOC(int,10);
//...
free(p);
return 0;
}
它和函数的功能很相似,但是它没有类型检查,而且宏也不能递归。
在执行简单的程序时,宏由于是直接把代码替换的,所以它的运行速度比函数要快。但是在复杂的程序中,宏的多次使用会使代码量大大提升。而函数始终只有一份,用的时候调用一次。此时用函数更好。
宏的使用不参与调试,它在预处理就直接替换了,所以没办法在调试中显示出来。
宏没有类型检验,相对来说,没有函数严谨。
宏的参数在字符串里是,不会被替换。比如“type = 3”,就算参数是type,预处理阶段也只会把它当作字符串常量来处理。
#是把宏的参数字符串常量化。
#define PRINT(n) printf("the value of "#n" is %d\n",n)
假设传的是num,预处理之后,PRINT(m)的地方就会被下面的语句所替代。
printf (“the value of num is %d\n",num)
##
#define GENERIC_MAX(type)\
type type##_max(type x, type y) \
{\
return ((x) > (y))?(x):(y);\
}
##就是把前面的type和后面的_max连在一起当作一个整体。这里表示的是一个函数名,当我们要使用这个定义的函数时,如果不用##,这个type不会被当作参数转换,那么每次调用的函数名就是重复的,在编译时会报错。声明不够清晰。
编译主要处理词法分析、语法分析和语义分析。
词法分析主要是把一个语句中包含的每一个关键字、标识符、字面量、特殊字符等进行分析。
语法分析主要是产生语句中的各种表达式为节点的数。
语义分析主要是包含类型的声明和匹配,类型的转换等,这个阶段会报告错误的语法信息。
有些代码删除了可惜,保留了有时有点多余,所以用条件编译指令。
#if? 常量表达式
#endif
//如果常量表达式为真,则编译以下代码。然后用#endif结束。下面的类似。不再解释。
#ifdef? //#if defined(? ? )
#endif
#ifndef//#if !defined(? ? )
#endif
#if? 常量表达式
#elif? 常量表达式
#elif? ?常量表达式
#else
以上配对使用。
汇编就是把汇编代码转换成机器可执行的指令,这个过程也不做指令优化。会生成一个符号表。符号表里是变量和它对应的地址,函数名和它对应的地址等等。
汇编之后会生成一个.o为后缀的目标文件。
链接主要是把这些符号表和段表合并和重新定位。地址和空间的分配。把各个文件链接在一起生成一个可执行程序。这个过程因为会重定义,所以变量对应的无效的地址,没有声明的函数等都会被发现,然后报错。
链接解决的是一个项目中多文件多模块互相调用的问题。
1.程序必须载入内存中,在有操作系统的环境中,这个由操作系统完成,独立的环境中,由手动安排。
2.程序执行开始,调用main函数。
3.开始执行代码,调用堆栈,或者静态内存(全局变量,static修饰的变量)。
4.终止程序。