个人博客网址:https://ljsblog.com
预处理(十二-终)
翻译环境和执行环境
翻译环境
在这个环境中源代码被转换为可执行的机器指令,就是把(.c)文件翻译成(.exe)文件。
一个C语言程序需要经过的四个步骤:编辑(.c)、编译(.obj)、链接(.exe)、运行。
而其中编译又分为:预编译(预处理)、编译、汇编。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vuyDllJe-1625748038088)(过程.png)]
预编译(预处理)
源码中的所有预处理语句(#号开头的语句便是预处理语句,例如:#include)得到处理并删除注释。
编译
就是把C语言代码翻译成汇编代码。
汇编
把汇编代码翻译成二进制代码。
链接
但机器代码还是不能直接运行。所以链接器将处理合并目标代码,生成一个可执行目标文件,可以被加载到内存中,由系统执行。
执行环境
这个环境用于实际执行代码。
- 程序必须载入内存中。在有操作系统的环境中,一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储在静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
预编译(预处理)详解
预定义符号
__FILE__//源文件的绝对路径
__LINE__//文件当前的行号
__DATE__//文件被编译的日期
__TIME__//文件被编译的时间
例
#include <stdio.h>
int main()
{
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
return 0;
}
/*
d:\vc++2010\prac\prac\ss.c
5
Feb 7 2021
22:29:26
*/
预处理指令
#开头的就是预处理指令例如:
#define,#include,#pragma pack(),#if,#endif,#ifdef,#line
#define定义标识符
#define name stuff
例:
#include <stdio.h>
#define MAX 100
#define STR "blog"
int main()
{
int a=MAX;
//预处理完后变为
//int a=100;
printf("%d\n",a);//100
printf("%s\n",STR);//blog
return 0;
}
注:在define定义标识符时,最后不要加;
#define定义宏
#define可以把参数替换到文本中,这种实现称为宏(macro)或定义宏(define macro)。
宏的申明方式:
#define name(parament-list) stuff
parament-list是一个由逗号隔开的符号表,这些符号可能出现在stuff中。
参数列表的左括号必须与name紧邻
例:
#include <stdio.h>
#define SQU(x) x*x
//接收一个参数x
int main()
{
int a=SQU(3);
//预处理器会将3*3代替SQU(3)
printf("%d\n",a);//9
return 0;
}
注:宏的参数不是传参,而是替换
例:
#include <stdio.h>
#define SQU(x) x*x
int main()
{
int a=SQU(3+1);
//预处理将 3+1*3+1 代替SQU(3+1),并不是(3+1)*(3+1)
//所以a的值为7,而不是16
printf("%d\n",a);//7
}
所以为了避免这个问题,我们要给stuff加上括号(stuff)以及stuff中每个参数也加上括号。
#include <stdio.h>
#define SQU(x) ((x)*(x))
int main()
{
int a=SQU(3+1);
//预处理将 ((3+1)*(3+1)) 代替SQU(3+1)
//所以这次a的值为16
printf("%d\n",a);//16
return 0;
}
注:
- 宏参数和#define定义中可以出现其他#define的变量,但宏不能出现递归。
- 预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索,通俗点讲,就是字符串常量的内容不会被替换。
#和##的区别
#是将其后面的宏参数进行字符串化操作,并不替换。
例:
#include <stdio.h>
#define PRINT(x) printf(#x"的值为%d\n",x)
int main()
{
int a=10;
int b=20;
PRINT(a);
//printf(“a”“的值为%d\n”,x)替换PRINT(a)
PRINT(b);
//printf(“b”“的值为%d\n”,x)替换PRINT(a)
return 0;
}
/*
a的值为10
b的值为20
*/
##是将它两边的符号合成一个符号。
例:
#include <stdio.h>
#define CON(x,y) x##y
int main()
{
int myblog2021=100;
printf("%d\n",CON(myblog,2021));//100
//CON(myblog,2021)
//myblog##2021
//myblog2021
return 0;
}
宏和函数
区别
- 宏的参数与类型是无关的,只要对参数的操作合法,就可以使用于任何类型;函数的参数类型是固定的,如果参数的类型不同,就需要使用不同的函数,即使他们执行的任务是相同的。
- 宏可能会带来运算优先级的问题,导致运算结果出错;
- 宏的参数替换是直接替换的;而函数调用时会将实参的值传给形参;
- 宏是不方便调试的,因为宏是在编译之前进行(先用宏体替换宏名,再进行编译);而函数是可以逐语句调试;
- 宏的参数是不占内存空间的,因为只做字符串的替换;而函数调用时参数之间的传递,所以占用内存;
- 宏的速度比函数速度快,因为函数有调用和返回时间的开销;
- 宏在传参时可以传类型,但是函数不能传类型;
- 宏不能递归,函数可以递归;
- 宏的代码长度很长(除去非常小的宏),每次使用时,宏代码都被插入到程序中,使得程序的长度大幅度增加;
命名约定
宏名全部大写,函数名不要全部大写。
#undef
用于移除一个宏定义。
#undef name
例:
#include <stdio.h>
#define MAX 100
int main()
{
printf("%d\n",MAX);//100
#undef MAX
return 0;
}
条件编译
选择性的按照某种条件编译代码。
常见的条件编译指令:
//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
文件包含
#include指令可以使另外一个文件被编译。就像它实际出现于#include指令的地方一样。
替换方式:预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。
头文件被包含方式
本地文件包含
#include "filename.h"
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误。
库文件包含
#include <filename.h>
编译器直接在标准位置查找头文件。如果找不到就提示编译错误。