详解C语言预处理&程序环境

目录

一,程序环境

1.程序的翻译环境

程序编译与链接

2. 程序的运行环境

二,预处理指令

         预定义符号

1. #define

用法1:#define name stuff 

用法2:#define 定义宏

易错点:

宏和函数优劣对比

 2. #和## 

   #  的功能

   ##的功能

 3. #undef:  

 4. 条件编译

 5. #include

 如何解决头文件多重包含?


一,程序环境

        在 ANSIC 的任何一种实现中,存在两个不同的环境
第1种是 翻译环境 ,在这个环境中源代码被转换为可执行的机器指令。
第2种是 执行环境 ,它用于实际执行代码。

1.程序的翻译环境

程序编译与链接

 程序由被人所能认识的源代码,经过翻译转化成能被机器所能识别的二进制语言。过程大致如下:

其中,注意: 

  • 组成一个程序的每个源文件通过编译过程分别转换目标代码object code)。
  • 每个目标文件由链接器linker)捆绑在一起,形成一个单一而完整的可执行程序。
  • 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库, 将其需要的函数也链接到程序中。

        

 其中的详细步骤,及各步骤的功能,如图:

当我们翻译成了机器语言后,就可以准备运行了。

2. 程序的运行环境

程序执行的过程
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须 由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用 main 函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈 stack ),存储函数的局部变量和返回地址。程序同时也可以使用静态(static )内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止 main 函数;也有可能是意外终止。

二,预处理指令

 预定义符号

预定义符号
这些预定义符号都是语言内置的。
比如:
__FILE__       // 进行编译的源文件
__LINE__     // 文件当前的行号
__DATE__     // 文件被编译的日期
__TIME__     // 文件被编译的时间
__STDC__     // 如果编译器遵循 ANSI C ,其值为 1 ,否则未定义

如: 

printf ( "file:%s line:%d\n" , __FILE__ , __LINE__ );
// 第一个就会打印源文件的路径; 第二个就会打印此段代码所在的行号

1. #define

这里就是我们最熟悉的#define 了。

用法1:#define name stuff 

#define k  20   // 定义个常量

   (注:这里后面不要加分号“;”,因为在预编译时,stuff会替换程序中所有的name,所以你要的是stuff,不是stuff;)

用法2:#define 定义宏

下面是宏的申明方式:

       #define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。

#define text(x)   2 * x

(注:#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。)

易错点:

1. 参数列表的左括号 必须 name 紧邻 。 如果两者之间有任何空白存在,参数列表就会被解释为 stuff 的一部分。
比如:
#define SQUARE( x )    x * x  // 正确的写法
2. 替换目标尽量用括号包裹,避免出现运算顺序错误。
#define text(x, y)     x + y          // 高危代码,非常容易发生错误
//  比如:printf("%d", 2 * text(2, 3));   // 输出是:7   而你想要的是10,所以正确的写法应该是
#define text(x, y)     ((x) + (y)) 
//  所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或
邻近操作符之间 不可预料的相互作用
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的变量。但是对于宏,不能出现递归
2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索

宏和函数优劣对比

(注:建议宽屏浏览

性                                              #define 定义宏                                                                函数
代码长度           每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,      每次使 用这个函数时,都调用那个地方的同 一份代码
                         程序的长度会大幅度增长函数代码只出现于一个地方;
执行速度                                             更快                                                                     存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级        宏参数的求值是在所有周围表达式的上下文环境里,                       函数参数只在函数调用的时候求值一次,它的结果值传递
                              除非加上括号,否则邻近操作符的优先级可能会产生                       给函数。表达式的求值结果更容易预测。 
                              不可预料的后果,所以建议宏在书写的时候多些括号。                                                              
带有副作用         参数可能被替换到宏体中的多个位置,所以带有副作用的参                函数参数只在传参的时候求值一次,结果更容易控制。
 的参数                数求值可能会产生不可预料的结果。
参数类型          宏的参数与类型无关,只要对参数的操作是合法的,它就可以                 函数的参数是与类型有关的,如果参 数的类型不同,就                                                                                                                                                  需要不同的函数, 即使他们执行的任务是不同的。
                                                                                                                                       以使用于任何参数类型。
调 试                  宏是不方便调试的                                                                                           函数是可以逐语句调试的                                                    
递归                   宏是不能递归的                                                                           函数是可以递归的

2. #和## 

思考:如何把参数插入到字符串中?

#的功能

 首先我们看看这样的代码:

char* p = "hello ""bit\n";
    printf("hello ""bit\n");
    printf("%s\n", p);

 从结果上我们会发现都打印了 hello bit 。我们发现字符串是有自动连接的特点 。

   另外一个技巧是: 使用 # 把一个宏参数变成对应的字符串

比如:

第一种:#define text(x)  printf("I " "x" "China\n");  //参数x 需要是个字符串

第二种:#define text(x)  printf("I "#x"China\n");    // x同样要是字符串

##的功能:

   ##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。
#define ADD_TO_SUM(num, value)
sum##num += value;
...
ADD_TO_SUM(5, 10);// 作用是:给 sum5 增加 10.
注: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

 3. #undef:  

这条指令用于移除一个宏定义。
#undef NAME            //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

4. 条件编译

比如说:
         调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。 (底层对库进行修改使用比较广泛)
如:
#include <stdio.h>
#define max 2
int main()
{
#if max == 1
	printf("hehe\n");  // 不编译
#elif max == 2
	printf("baba\n");  // 编译
#else
	printf("gungun\n");  // 不编译
#endif // 1
	return 0;
}

5. #include

    我们已经知道, #include 指令可以使 另外一个文件 被编译。就像它实际出现于 #include 指令的地方一样。
这种替换的方式很简单: 预处理器先删除这条指令,并用包含文件的内容替换。 这样一个源文件被包含10 次,那就实际被编译10 次。(多次包含很浪费资源)
    我们会注意到:
#include  <stdio.h>   
#include "text.h"       //  自建头文件 

有“”  与  <>  两种,其实是两种查找策略:

  •  “”   :先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头 文件。 如果找不到就提示编译错误。
  • <> :   查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。(库文件自带的安装路径)

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

 如何解决头文件多重包含?

为什么要解决头文件多重包含,如果头文件被多次包含,会出现重复定义的问题。

解决办法:

  • 高级的: 头文件开头 加上 #pragma once

 #pragma once

  • 手动添加: 在头文件开头
#ifndef _text_h_   // 没定义 返回1;否则返回0,结束编译
#define _text_h_

// ....... 各种包含头文件,函数声明
int k(int x, int y);
// .......

#endif // !_text_h_

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

  • 35
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 50
    评论
C语言中,预处理是指在编译阶段之前对源代码进行处理的过程,它由编译器的预处理器执行。预处理器会根据以符号#开头的预处理命令,对源代码进行一系列的处理和替换操作。预处理命令可以用来包含头文件、定义宏、条件编译等。 其中,预处理命令#include用于包含其他文件。被包含的文件可以是普通的C语言程序文件(.c文件),也可以是C语言程序的头文件(.h文件)。在C语言系统中,大量的定义与声明是以头文件形式提供的。通过#include命令,我们可以将头文件中的内容插入到当前源文件中,以便在程序中使用头文件中定义的函数、变量或宏等。 总结起来,预处理C语言中的用法主要包括使用#include命令来包含头文件,以及其他预处理命令用于定义宏、进行条件编译等操作。这些预处理命令能够扩展C语言程序设计的环境,提供了更灵活和高效的代码编写方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C语言中的预处理详解](https://blog.csdn.net/weixin_32445333/article/details/117124370)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 50
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值