预处理和宏定义【C语言】

发布时间:2023-05-26 08:30

        任何一个ANSI C,都有两个环境,一个翻译环境一个运行环境。

        运行环境相当于windows,翻译环境相当于编译器。


一、程序的编译和预处理:

        一个程序从代码到执行程序,要进行一系列复杂的工作,在vs2022的编译器中,这些工作都我们完成了。都这里只简单讨论其执行步骤(如果想深入了解推荐《程序员的自我修养》)。     

        vs2022是集成开发环境(IDE),其内部集成和编译器(cl.exe)和连接器(link.exe),来帮助使用者快速和方便的编译代码,最后生成可执行程序.exe。这里到生成可执行程序的步骤,可以统称为编译。

        但是在某些编译器上则需要自己手动进行编译和处理。

编译环境:

        这里把一个代码文件test.c生成了一个可执行test.exe  

预处理和宏定义【C语言】_第1张图片

 步骤:

1. 预处理(预编译)  生成中间件 test.i

预处理和宏定义【C语言】_第2张图片

这里会发现,中间件因为加载了头文件,代码行数到了六万多行。还删除了注释。并且把#define定义的符号进行了替换。

2.编译结果保存在test.s中。中间件test.s中保存的就是源代码的汇编代码。

3.汇编完成,结果保存在test.obj中。是一堆二进制指令和形成符号表。

预处理和宏定义【C语言】_第3张图片

4.最后进行链接,进行到这里的时候编译器就能判断外部函数是否正确的被调用。比对符号表,发现符号表上并没有找的对应的函数就会报错。 

预处理和宏定义【C语言】_第4张图片

运行环境:  

1.一个程序要执行,必须载入内存。在windows下,是系统帮我们载入的,在其他平台上需要自己手动烧录。

2.程序的开始main()函数。

3.开始执行程序代码。

4.终止程序。

二、预处理

        #include 和 #define都是预处理指令,一个是用来加载头文件,一个是用来宏定义。

        在vs编译器内部有很多的宏定义

  • __FILE__           当前文件
  • __LINE__          当前代码行
  • __TIME__          文件被编译的时间
  • __DATE__         文件被编译的日期
  • __func__           当前函数名
  • __STDC__        编译器遵循ANSI C,其值为1

         预处理和宏定义【C语言】_第5张图片

        当然我们也可以自己定义一个宏,这时就需要使用预处理命令#define。

预处理和宏定义【C语言】_第6张图片

         这里定义了一个MAX(x,y),会发现相较于写一个比较大小的函数,这种写法简洁方便。但是要注意,#define并不是如同函数那样接收参数,而是单纯的符号替换。

        第二个文件为程序编译时,产生的中间件。这里会发现这里#define定义的宏,完全是替换的功能 并不像函数那样在其内部进行计算,所以没有在栈区产生消耗,所以速度相较于函数来说比较快一些。

预处理和宏定义【C语言】_第7张图片

        但是因为其是单纯的符号替换,所以这里必需要注意符号的优先级

         预处理和宏定义【C语言】_第8张图片

        比如这里只是想计算10乘以比较大小后的数,但是这里进行替换后却发现10*10是先计算的,会造成错误。所以这里要注意必需要加上括号。 

预处理和宏定义【C语言】_第9张图片         但是如果只对单个的参数括了起来,还是会出现错误,所以这里需要把整体一起括起来

预处理和宏定义【C语言】_第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++);

        预处理和宏定义【C语言】_第11张图片

        这里其实就会发现,前面变量计算后,影响到了后面的值。所求的值跟原来进行比较的值已经不一样了。

预处理和宏定义【C语言】_第12张图片        

        对应一个函数来说其参数据有固定的类型,要进行不同类型的比较,要定义多个函数,但是宏没有类型检查,可以接受任意类型的数据。

预处理和宏定义【C语言】_第13张图片

        宏没办法像函数一样进行调试,宏也不能递归。

        所以要进行宏定义的时候,要根据自己需要来选择要不要使用。 

#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
    //...
#endif

          这种写法也可以防止多次加载头文件,并且兼容性好。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号