任何一个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
          这种写法也可以防止多次加载头文件,并且兼容性好。

技术
©2019-2020 Toolsou All rights reserved,
TypeScript:函数类型接口8道大厂指针笔试题让你秒杀指针!!!MySQL 日期时间加减mysql 查询条件之外的数据_mysql 查询符合条件的数据查linux的操作系统版本,如何查看Linux操作系统版本?将String类型转换成Map数据类型使用uuid做MySQL主键,被老板,爆怼一顿C语言中的字符串函数和字符函数linux服务器中毒排查--基础篇C# ASCII码字符转换