秋已深
冬将至
人间至此秋色尽
一世繁华一世荒

程序的编译与预处理

  • 程序环境
    • 程序翻译环境
    • 程序执行环境
  • 程序预处理
    • 相关符号介绍
      • ️预定义符号
      • ️#define定义标识符
      • ️define定义宏
    • 宏和函数的对比
    • 其它预处理命名

程序环境

程序翻译环境

在这个环境中源代码被转换为可执行的机器指令。

程序执行环境

1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

2. 程序的执行便开始。接着便调用main函数。

3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4. 终止程序。正常终止main函数;也有可能是意外终止。

程序预处理

相关符号介绍

️预定义符号

NULL:表示空指针,用于指示指针变量没有指向任何对象。

EOF:表示文件结束符,用于在文件输入时表示已经到达文件末尾。

sizeof:用于获取数据类型或变量所占用的内存空间大小。

const:用于声明常量,表示其值不可修改。

volatile:用于声明变量的值可能会在程序运行期间被意外修改。

signed:用于声明有符号类型的变量,可以表示正数和负数。

unsigned:用于声明无符号类型的变量,只能表示非负整数。

void:用于声明无类型类型的变量或函数,表示不返回任何值。

do-while:用于创建循环结构,先执行循环体,再判断循环条件。

for:用于创建循环结构,通常用于需要执行固定次数的循环操作。

if:用于条件判断,根据条件是否成立来执行不同的代码块。

switch:用于多分支条件判断,根据不同的条件执行不同的代码块。

while:用于循环结构,先判断循环条件,再执行循环体。

……

这些都是一些普通的预定义符号

__FILE__//进行编译的源文件__LINE__ //文件当前的行号__DATE__//文件被编译的日期__TIME__//文件被编译的时间__STDC__//如果编译器遵循ANSI C,其值为1,否则未定义

这些都是C语言内置的预定义符号。大家可以试着去编译下,看看效果,平常我们都很少用到。

️#define定义标识符

在C语言中,#define是预处理指令,它用来定义标识符或常量。当你使用#define定义一个标识符时,编译器会将其替换为你在定义时指定的文本。例如,你可以使用#define定义一个常量:

#define PI 3.14159//在这个例子中,当你使用PI时,编译器会将其替换为3.14159。
#definemax 100//在这个例子中,当你使用PI时,编译器会将其替换为3.14159。

️define定义宏

#define Add(x) ((x) * (x))//在这个例子中,Add(x)会被替换为((x) * (x))。//注意宏的参数在替换时需要用括号括起来,以避免因为运算符优先级产生的问题。//例如,如果你不使用括号,Add(x + 2)会被展开为x + 2 * x + 2,而不是你预期的(x + 2) * (x + 2)。

需要注意的是,#define定义的标识符在预处理阶段就会被展开,而不会在运行时进行计算。因此,使用宏进行计算可能会影响程序的性能。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但 是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

宏和函数的对比

宏和函数在C语言中都用于实现代码的复用和简化,但它们在实现方式和用途上有一些不同。

执行方式:函数在程序运行时被调用,执行对应的代码块。宏在预处理阶段进行文本替换,不占用函数调用的时间和空间。

类型检查:函数具有强类型检查,编译器会检查函数参数类型和返回值类型。宏在替换时不会进行类型检查,容易出现错误。

安全性:函数是安全的,因为它们有参数类型检查和返回值。宏可能会出现错误或不安全的表达式。

调试:函数可以设置断点进行调试,而宏不能进行调试。

作用域:函数具有局部作用域,宏具有全局作用域。

性能:函数调用需要一定的开销,包括保存寄存器、栈操作等。宏替换没有这些开销,但可能会增加代码大小,降低运行速度。

使用场景:函数是可重用的代码块,通常用于封装复杂逻辑。宏通常用于替换简单的文本或常量,提高代码可读性。

在实际编程中,应根据具体需求选择使用宏还是函数。如果需要类型检查、安全性高或需要调试的代码块,应使用函数。如果需要替换简单的文本或常量,提高代码可读性,可以使用宏。

其它预处理命名

除了#define,C语言中还有其他一些预处理命名,包括:

#include:用于包含头文件,将头文件中的代码插入到程序中。

#ifndef、#define、#endif:用于防止头文件被重复包含。

#if、#elif、#else、#endif:用于条件编译,根据条件决定是否编译某段代码。

#warning:用于在编译时输出警告信息。

#error:用于在编译时输出错误信息。

#line:用于改变预处理器所报告的当前文件名和行号。

#pragma:用于改变编译器的特定设置。

这些预处理命名都是在编译阶段进行处理的,可以用来控制编译过程、生成特定代码或输出信息等。