- 程序的翻译环境和执行环境
- 编译+链接
- 预处理
一.程序的翻译环境和执行环境
在ANSI C标准的任何一种实现中,存在两种不同的环境:
- 翻译环境:该环境中 源代码 会被转换为 可执行的机器指令
- 执行环境:其用于实际 执行代码
二.编译+链接
2.1翻译环境
- 一个源文件经过编译器处理形成一个目标文件,目标文件被链接器链接形成可执行程序
- 翻译环境是 编译 和 链接
2.2翻译环境的几个阶段
2.3运行环境
程序执行的过程:
- 程序必须载入内存中。
在有操作系统的环境中:程序的载入一般由操作系统完成。
在独立环境中:程序的载入必须手工安排,也可能是通过可执行代码置入只读内存来完成。
2.程序的执行便开始。接着便调用 main 函数。
3.开始执行程序代码,这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(staic)内存,存储于静态内存中的变量在程序整个执行过程中一直保留它们的值。
4.终止程序。正常终止main函数,也可能是意外终止
三.预处理
3.1预定义符号
- 在预处理阶段被处理的已经定义好的符号,可以拿来直接用
__FILE__ // 代码所在的 源文件,路径
__LINE__ // 代码所在的 行号
__DATE__ // 程序被编译的 日期
__TIME__ // 程序被编译的 时间
__FUNCTION__ // 代码所在函数的 函数名
- 左下和右下都是2个下划线
- 如果一个工程特别复杂,这时去调试时可能会无从下手。所以需要代码在运行的过程中记录一些日志信息,即可通过日志信息看程序哪里出了问题
例子:
#include <stdio.h>
int main()
{
int i = 0;
FILE* pf = fopen("acc.txt", "a+");
if (pf == NULL)
{
perror("fopen\n");
return 1;
}
for (i = 0; i < 10; i++)
{
fprintf(pf,"%s %d %s %d\n",__FILE__, __LINE__, __DATE__,__TIME__ );
}
fclose(pf);
pf = NULL;
return 0;
}
- 除以上5个外,还有一个 __STDC__,如果编译器遵循 ANSI C 标准则返回1,否则未定义
在Dev-c++上有1,表明其遵循 ANSI C 标准
3.2#define
3.2.1 #define 定义标识符
语法:
#define name stuff
//例子:
//#define MAX 100
//#define reg register
注意:#define 定义标识符 ,其末尾不用再加个分号
例子:
#include <stdio.h>
#define M 100
//预处理阶段就会将M替换成100
int main()
{
int m = M;
printf("%d\n",m);
return 0;
}
//100
3.2.2 #define 定义宏
- #define 机制有个规定,允许把参数替换到文本中,这种实现成为宏或定义宏
申明方式:
#define name( parament-list ) stuff
//name和左括号必须紧紧在一起,否则后面的参数会被当成stuff的一部分
举例:
#include <stdio.h>
#define SQUARE(X) X*X
int main()
{
printf("%d\n", SQUARE(3));//实现后为 printf("%d\n", 3*3);
return 0;
}
//9
// 先将函数中name()中的参数替换 掉宏中的参数,再将stuff的结果返回函数去 替换掉name()
特别注意:宏的参数是完全替换的,不事先计算再传参
例子:用类似上述方法得出16
#include <stdio.h>
#define SQUARE(X) X*X
int main()
{
printf("%d\n", SQUARE(3+1));
//实现后为 printf("%d\n", 3+1*3+1);
return 0;
}
//7
//这样明显不对,So在敲的时候我们可以带上()来防止出错
#include <stdio.h>
#define SQUARE(X) (X)*(X)
int main()
{
printf("%d\n", SQUARE(3+1));
return 0;
}
//16
注意下面这种情况:
#include <stdio.h>
#define SQUARE(X) (X)+(X)
int main()
{
printf("%d\n", 10 * SQUARE(4));
return 0;
}
// 实现后是 printf("%d\n", 10 * (4) + (4));
//所以结果是44
3.2.3 #define 替换规则
在程序中扩展#define定义符号和宏时,几个步骤:
- 在调用宏时,首先对参数进行检查,看是否包含任何由 #define 定义的符号。如果是,它们先被替换
- 替换文本随后被插入到程序中原来的文本位置。对于宏,函数名被它们的值替换
- 再次对结果文件进行扫描,看是否包含任何由 #define 定义的符号。如果包含,就重复上述过程。
注意:
- 宏参数 和 #define 定义中可以出现其他 #define 定义的常量。但对于宏,不能出现递归
- 当预处理器 搜索 #define 定义的符号的时候,字符串常量中的内容不被搜索,即不会被替换
#include <stdio.h>
#define M 100
int main()
{
printf("M = %d\n", M);//前面的M不会被替换
return 0;
}
//100
3.3命名约定
由于一般来讲,函数和宏的使用语法相似,所以为了区分它们,用一个习惯是:宏名全部大写,函数名不要全部大写
3.4#undef
该命令用于 移除一个宏定义
语法格式:
#undef NAME
例子:
#include <stdio.h>
#define M 100
int main()
{
int a = M;
#undef M
printf("%d\n", M);
return 0;
}
3.5 条件编译
在编译一个程序时,可以用条件编译将一条语句或一组语句编译/放弃是很方便的
#include <stdio.h>
#define __DEBUG__
int main()
{
int arr[10] = {0};
int i = 0;
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d ", arr[i]);//如果定义了则会编译 ,没有定义则不会参与编译
#endif
}
return 0;
}
//如果定义了则会编译 ,没有定义则不会参与编译