编译+链接
C语言执行的过程
我们知道机器最终实现的为01序列的二进制代码,而我们在VS等IDE下编写的其实是一串串的文字。首先编译器会将我们写的代码全部转化为字符(利用ASCⅡ码)存储,根据不同的指令再通过编译转化为汇编语言,此时高级语言已经达到了机器语言的层面,汇编语言再次转化为二进制语言,生成了obj文件(xxx.o),送入链接器,生成可执行文件(xxx.exe)
编译
编译分为三个步骤预编译、编译、汇编。为了更好的了解,我们可以通过linux命令行了解生成的模式。原始文件如下
预编译
1、头文件包含
2、#define 的替换
3、注释的删除
生成
编译
此阶段
1、语法分析
2、词法分析
3、语义分析
4、符号汇总
生成
汇编
此阶段将汇编代码转换成二进制指令,形成符号表
#
生成
链接
此阶段
1、合并段表
2、符号表的合并和重定位
生成可执行文件
linux环境下 可执行文件的后缀名为.out
预处理指令
预处理符号
FILE //进行编译的源文件
LINE //文件当前的行号
DATE //文件被编译的日期
TIME //文件被编译的时间
STDC //如果编译器遵循ANSI C,其值为1,否则未定义
宏定义
宏本质是再编译阶段将程序主题内所有出现的定义变量位置进行替换,所以最好不要加上;
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
#define M 100
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
宏定义函数与函数
1、宏可以模拟函数定义
#define SQUARE(x) x*x
int main(){
printf("%d",SQUARE(5));//替换后为 5*5=25
return 0;
}
然而这种定义方式有可能会造成优先级错误
#define SQUARE(x) x*x
int main(){
printf("%d",SQUARE(5+1));//替换后为 5+1*5+1=11
return 0;
}
或是
#define double(x) (x)+(x)
int main(){
printf("%d",2*SQUARE(5+1));//替换后为 2*(5+1)+(5+1)=18
return 0;
}
所以通常采用
#define SQUARE(X) ((X)*(X))
2、宏与函数的对比
宏一般用来进行简单运算
#define MAX(a, b) ((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务? 原因有二:
- 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序
的规模和速度方面更胜一筹。 - 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可
以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方: - 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
一些特殊的预处理指令
1、# 将需要替代的变量 以字符串形式替换
如例子:需要定义宏 print(format,value)使得num以format形式打印
#define PRINT(FORMAT, VALUE)\
printf("the value of " #VALUE "is "FORMAT "\n", VALUE);//打印出来为 the value of a is 10
2.## 链接两个变量
#define ADD_TO_SUM(num, value)
sum##num += value;
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.
3、#undef 用于移除一个定义 作用与#define 相反
4、命令行定义
有如下代码
#include <stdio.h>
int main()
{
int array [ARRAY_SIZE];
int i = 0;
for(i = 0; i< ARRAY_SIZE; i ++)
{
array[i] = i;
}
for(i = 0; i< ARRAY_SIZE; i ++)
{
printf("%d " ,array[i]);
}
printf("\n" );
return 0;
}
我们观察到ARRAY_SIZE并未定义
再Linux环境中可以利用命令行定义
gcc -D ARRAY_SIZE=10 programe.c
4、条件编译
#有if 就有endif
#ifndef 如果没有定义就参与编译,如果定义了就不参与编译
#ifdef 或#if defined() 如果定义了就参与编译,如果没有定义就不参与编译
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
5、防止头文件的多次引用
第一种方式
第二种方式
#pragma once