任何一个ANSI C,都有两个环境,一个翻译环境一个运行环境。
运行环境相当于windows,翻译环境相当于编译器。
一、程序的编译和预处理:
一个程序从代码到执行程序,要进行一系列复杂的工作,在vs2022的编译器中,这些工作都我们完成了。都这里只简单讨论其执行步骤(如果想深入了解推荐《程序员的自我修养》)。
vs2022是集成开发环境(IDE),其内部集成和编译器(cl.exe)和连接器(link.exe),来帮助使用者快速和方便的编译代码,最后生成可执行程序.exe。这里到生成可执行程序的步骤,可以统称为编译。
但是在某些编译器上则需要自己手动进行编译和处理。
编译环境:
这里把一个代码文件test.c生成了一个可执行test.exe
步骤:
1. 预处理(预编译) 生成中间件 test.i
这里会发现,中间件因为加载了头文件,代码行数到了六万多行。还删除了注释。并且把#define定义的符号进行了替换。
2.编译结果保存在test.s中。中间件test.s中保存的就是源代码的汇编代码。
3.汇编完成,结果保存在test.obj中。是一堆二进制指令和形成符号表。
4.最后进行链接,进行到这里的时候编译器就能判断外部函数是否正确的被调用。比对符号表,发现符号表上并没有找的对应的函数就会报错。
运行环境:
1.一个程序要执行,必须载入内存。在windows下,是系统帮我们载入的,在其他平台上需要自己手动烧录。
2.程序的开始main()函数。
3.开始执行程序代码。
4.终止程序。
二、预处理
#include 和 #define都是预处理指令,一个是用来加载头文件,一个是用来宏定义。
在vs编译器内部有很多的宏定义
* __FILE__ 当前文件
* __LINE__ 当前代码行
* __TIME__ 文件被编译的时间
* __DATE__ 文件被编译的日期
* __func__ 当前函数名
* __STDC__ 编译器遵循ANSI C,其值为1
当然我们也可以自己定义一个宏,这时就需要使用预处理命令#define。
这里定义了一个MAX(x,y)
,会发现相较于写一个比较大小的函数,这种写法简洁方便。但是要注意,#define并不是如同函数那样接收参数,而是单纯的符号替换。
第二个文件为程序编译时,产生的中间件。这里会发现这里#define定义的宏,完全是替换的功能。
并不像函数那样在其内部进行计算,所以没有在栈区产生消耗,所以速度相较于函数来说比较快一些。
但是因为其是单纯的符号替换,所以这里必需要注意符号的优先级
比如这里只是想计算10乘以比较大小后的数,但是这里进行替换后却发现10*10是先计算的,会造成错误。所以这里要注意必需要加上括号。
但是如果只对单个的参数括了起来,还是会出现错误,所以这里需要把整体一起括起来
而且,#define定义的宏,输入的参数不能带副作用(a+1没有副作用,但是a++有副作用,其改变了a本身的值)
#define MAX(x,y) ((x)>(y)?(x):(y)) int a=5; int b=8; int c=MAX(a++,b++);
这里其实就会发现,前面变量计算后,影响到了后面的值。所求的值跟原来进行比较的值已经不一样了。
对应一个函数来说其参数据有固定的类型,要进行不同类型的比较,要定义多个函数,但是宏没有类型检查,可以接受任意类型的数据。
宏没办法像函数一样进行调试,宏也不能递归。
所以要进行宏定义的时候,要根据自己需要来选择要不要使用。
#undef:移除宏定义
#define NUM 10; #undef NUM
三、条件编译:
#if 常量表达式 //... #endif #if 常量表达式 //... #elif 常量表达式 //... #else 常量表达式 //...
#endif #define NUM 1 #if NUM==1 printf("1\n"); #elif NUM==2 printf("2\n");
#else printf("3\n"); #endif
这段代码的含义是,如果NUM等于1,则打印1,如果NUM等于2,打印2,否则打印3。会发现跟 if() 语句非常相似。
#if defined(MAX) //... #endif //等价于下面 #ifdef MAX //... #endif
这段代码的含义是,如果MAX被宏定义,则执行后面的代码。
#if !defined(MAX) //... #endif //等价于下面 #ifndef MAX //... #endif
这段代码的含义是,如果MAX没有被宏定义,则执行后面的代码。
#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
条件编译指令可以嵌套使用,但是会发现代码可读性非常差,很难以理解。
PS:进入vs自带的库函数会发现,条件编译在vs底层非常常见。
避免头文件重复引用:
1.#pragma once
#pragma也是一个预处理指令,这里加一个once,一般写在头文件最上面,其意思是只加载一次头文件。但是这种写法在其他编译器上有可能不支持。所以还有一种普遍的写法。
#ifndef _TEST_H #define _TEST_H #includ<stdio.h> //... #endif
这种写法也可以防止多次加载头文件,并且兼容性好。
今日推荐