【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)

这篇具有很好参考价值的文章主要介绍了【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:
        这是程序环境和预处理的下半篇文章。至此,关于c语言知识点:从编译到运行的过程已讲解完毕。传送🚪,上半篇:
        本章涉及的知识点: 宏和函数对比、命名约定、#undef、命令行定义、条件编译、文件包含以及其他预处理指令。

目录

3. 预处理详解

3.2.6 宏和函数对比

3.2.7 命名约定

3.3 #undef

3.4 命令行定义

3.5 条件编译

1.常量表达式 

2.多个分支的条件编译

3.判断是否被定义

4.嵌套指令

 3.6 文件包含

3.6.1 头文件被包含的方式:

3.6.2 嵌套文件包含

4. 其他预处理指令


3. 预处理详解

3.2.6 宏和函数对比

        下面两种方式求两个数的较大值,谁优,谁劣?

#include<stdio.h>
//函数的实现
int Max(int x, int y)
{
	return x > y ? x : y;
}

//宏的实现
#define MAX(x,y) ((x)>(y)?(x):(y))

int main()
{
	int a = 0;
	int b = 0;
	//输入
	scanf("%d %d",&a,&b);
	//1.函数返回较大值
	int m1 = Max(a, b);
	printf("%d\n",m1);

	//2.使用宏 
	int m2 = MAX(a, b);//等价于 ((a)>(b)?(a):(b));
	printf("%d\n",m2);
	return 0;
}

宏通常被应用于执行简单的运算

比如在两个数中找出较大的一个

#define MAX(a, b) ((a)>(b)?(a):(b))

那为什么不用函数来完成这个任务?

原因有二:

1️⃣用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹
📚怎么理解呢:
从函数返回
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编
📃函数调用的时间花费:
1.函数调用前准备( 传参、函数栈帧空间的维护)
2.主要运算 
3.函数返回,返回值的处理,函数栈帧的销毁
涉及到函数栈帧的内容,传送门👉:
使用宏定义
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编
📃宏定义的时间花费:
2.主要运算(写成宏就把1,3步骤省略掉了) 不用建立函数栈帧,也就没有它的销毁

2️⃣更为重要的是函数的参数必须声明为特定的类型

        所以函数 只能在类型合适的表达式 上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型 宏是类型无关的

        如何理解:

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

由上面的两个原因,求两个数的较大值这个例子中,宏更有优势一些,使用宏定义可以省掉不必要去损耗的时间,那么宏是不是比函数更有优势呢?
宏的缺点: 当然和函数相比宏也有劣势的地方
1️⃣每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

2️⃣宏是没法调试的 

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

3️⃣ 宏由于类型无关,也就不够严谨
        只要能够参与运算,那么传入任何参数都能适用 ,这是一把双刃剑。
4️⃣宏可能会带来运算符优先级的问题,导致程容易出现错
        宏的劣势:当参数里面有表达式的时候,表达式传参传到宏的体内的时候,宏体内如果有相邻的操作符,这时候操作符优先级可能引起一些问题,导致程序错误。
        函数不会有这个问题,即使传入一个表达式,也会把它的值算出一个结果,再传进去.

        宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到(因为类型是不可能作为参数给函数传参的,函数传参传的是变量、数组、指针等)

 例子: 

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
    //函数传参
	int* p = (int*)mallloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc fail!");
		return;
	}

    //宏传参
	int* p2 = MALLOC(10, int);//类型作为参数,传参方便多了
	if (p2 == NULL)
	{
		perror("malloc fail!");
		return;
	}
    MALLOC(10,float);
}
        以后功能比较简单的时候,可以采用宏来实现如果功能比较复杂,建议使用函数来实现
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编
宏和函数的一个对比
属性 #define定义宏 函数
代码长度 每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长 函数代码只出现于一个地方;每 次使用这个函数时,都调用那个地方的同一份代码
执行速度 更快 存在函数的调用和返回的额外开 销,所以相对慢一些
操作 符优 先级 宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带 有 副 作 用 的 参 数 参数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果 函数参数只在传参的时候求值一 次,结果更容易控制。
参 数 类 型 宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。 函数的参数是与类型有关的,如 果参数的类型不同,就需要不同 的函数,即使他们执行的任务是 相同的。
调 试 宏是不方便调试的 函数是可以逐语句调试的
递 归 宏是不能递归的 函数是可以递归的

3.2.7 命名约定

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。

那我们平时的一个习惯是:

1.把宏名全部大写

2.函数名不要全部大写

3.3 #undef

这条指令用于移除一个宏定义

#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

演示:

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

3.4 命令行定义

1.许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个 程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

演示代码:

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

按ctrl+~键,看下图:按照下图先按住①再按②

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

把终端调出来

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

指定SZ(宏的大小)为10,即数组大小为10,那么依次打印1~10

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

指定SZ(宏的大小)为100,即数组大小为100,那么依次打印1~100

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

编译指令:

//linux 环境演示

gcc -D ARRAY_SIZE=10 programe.c

3.5 条件编译

        在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

比如说:

        调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译

#include <stdio.h>
#define __DEBUG__
int main()
{
     int i = 0;
     int arr[10] = {0};
     for(i=0; i<10; i++)
     {
     arr[i] = i;
     #ifdef __DEBUG__
         printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 
     #endif //__DEBUG__
     }
     return 0;
}

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

常见的条件编译指令:

1.常量表达式 

1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

注意:预处理期间,其实处理都是文本呀,代码处理的过程中,编译指令是有的,不需要编译的,就把它删了,需要后面编译的代码会留着。

所以右图中int a=2;不需要删除的原因在这里。
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

2.多个分支的条件编译

2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif

该编译放到这个代码里头,不该编译就删掉了 

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

3.判断是否被定义

if defined和ifdef是相同的,都是用于检查某个标识符是否已经定义的预处理指令

它们在C和C++中是等效的。

        使用 ifdef 或 if defined 可以根据某个标识符是否已经定义来进行条件编译。如果标识符已经通过 #define 或其他方式定义过,则执行 ifdef 或 if defined 后面的代码块;否则,忽略该代码块。

#define DEBUG_MODE

#ifdef DEBUG_MODE
    // 调试模式下的代码
    printf("执行调试代码\n");
    // ...
#endif

        在上述示例中,#define DEBUG_MODE 定义了一个名为 DEBUG_MODE 的宏。在 #ifdef DEBUG_MODE 的代码块中,可以放置调试模式下需要执行的代码。如果 DEBUG_MODE 宏已经被定义,那么代码块中的代码将会被执行;否则,代码块将被忽略。

        请注意,ifdef 和 if defined 仅用于在编译时进行条件判断,而不是在运行时。它们用于根据不同的编译配置或条件选择性地包含或排除代码块,从而实现更灵活的程序控制。

图解:

if defined(MAX)

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

#ifdef MAX 

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

把宏注释掉,用ifdef

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

同理可得!define和#ifndef:

#define

先来看没有用#define定义的时候,define(MAX)条件判断为假,!define(MAX)判断为真。

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

下面是已经定义的情况

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

 #ifndef

下面是 "#ifndef" 指令的基本语法:

#ifndef 宏名称
    // 如果宏名称未定义,则执行的代码
#endif

        如果名为 "宏名称" 的宏未定义,那么在预处理阶段将包含 "#ifndef" 块中的代码。如果该宏已定义,则会跳过块中的代码。

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

4.嵌套指令

#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
注意:上面条件编译只要有if,那么都用#endif来结束。

 3.6 文件包含

        我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。
这种替换的方式很简单:
        预处理器先删除这条指令,并用包含文件的内容替换。
        这样一个源文件被包含10 次,那就实际被编译 10 次。
 

3.6.1 头文件被包含的方式:

  • 本地文件包含

#include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。 如果找不到就提示编译错误。
路径:自己工程当前的目录查找

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

Linux 环境的标准头文件的路径:
/ usr / include
VS 环境的标准头文件的路径:
C : \Program Files ( x86 ) \Microsoft Visual Studio 12.0 \VC\include
// 这是 VS2013 的默认路径
注意按照自己的安装路径去找。
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编
  • 库文件包含

 #include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编
答案是肯定的, 可以。
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

3.6.2 嵌套文件包含

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

comm.h和comm.c是公共模块。

test1.h和test1.c使用了公共模块。

test2.h和test2.c使用了公共模块。

test.h和test.c使用了test1模块和test2模块。

这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复

如何解决这个问题? 答案:条件编译。

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

解决思路:

①使用#ifndef条件编译

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H__

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

②使用pragma once防止头文件被反复多次的包含 

#pragma once

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

vscode编译器:

【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下),C进阶,c语言,vscode,vs2019,编程语言,编译链接,汇编

以上①②两者写法均可防止文件重复包含。

注: 推荐《高质量C/C++编程指南》中附录的考试试卷(很重要)。

笔试题:

1. 头文件中的 ifndef/define/endif是干什么用的?

        头文件中的ifndef/define/endif是用于防止头文件被重复包含,以避免编译错误。ifndef用于判断某个标识符是否已经被定义,如果未被定义,则继续执行define指令,定义该标识符,并执行后续的代码;如果已经被定义,则跳过后续的代码,直接执行endif指令。这样可以确保头文件只被包含一次。

2. #include 和 #include "filename.h"有什么区别?

        #include <filename.h>是用于包含系统头文件,编译器会先在系统目录中查找该头文件;而#include "filename.h"是用于包含用户自定义的头文件,编译器会先在当前目录中查找该头文件,如果未找到,则会在系统目录中查找。

4. 其他预处理指令

#error
#pragma
#line
...
不做介绍,自己去了解。
#pragma pack()在结构体部分介绍。

参考《C语言深度解剖》学习

本章完。文章来源地址https://www.toymoban.com/news/detail-758407.html

到了这里,关于【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 程序环境和预处理(详解)

    🍕博客主页:️自信不孤单 🍬文章专栏:C语言 🍚代码仓库:破浪晓梦 🍭欢迎关注:欢迎大家点赞收藏+关注 本文重点 代码编译链接变成可执行程序程序的过程 掌握学习各种预处理知识 在ANSI C的任何一种实现中,存在两个不同的环境: 翻译环境,在这个环境中源代码被

    2023年04月27日
    浏览(30)
  • 【C】程序环境和预处理

    在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码。 翻译环境我们主要解释一下编译和链接过程。 我们的源文件也就是我们的.c文件通过编译器生成一个目标文件(

    2024年02月16日
    浏览(28)
  • C语言——程序环境和预处理(再也不用担心会忘记预处理的知识)

    先简单了解一下程序环境,然后详细总结翻译环境里的编译和链接,然后在总结编译预处理。 在 ANSI C 的任何一种实现中,存在两个不同的环境 翻译环境:这个环境中源代码被转换为可执行的机器指令。 执行环境:执行二进制代码。 计算机如何执行二进制指令? 我们写的C语

    2024年02月09日
    浏览(44)
  • 【C语言】程序环境和预处理

    本章重点 程序的编译环境 程序的执行环境 详解:C语言程序的编译加链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 #include 预处理指令 #undef 条件编译 程序的编译环境和执行环境 在ANSIC的任何一种实现中,存在两种不同

    2024年01月21日
    浏览(35)
  • C语言:程序环境和预处理

    目录 一,程序的翻译环境 二,详解编译+链接  2.1 翻译环境  2.2 程序编译的过程 2.3 运行环境 三,预处理详解  3.1 预定义符号 3.2 #define 3.2.1 查看预处理 3.2.2 #define 定义标识符  3.2.3 #define 定义宏  3.2.4#define 替换规则 3.25#和##  1.# 2.## 3.2.6 带副作用的宏参数 3.2.7 宏和函数对

    2024年02月10日
    浏览(41)
  • 程序环境和预处理(上)——“C”

    各位CSDN的uu们你们好呀,今天小雅兰的内容是C语言中的程序环境和预处理这个知识点,这块知识点是小雅兰地C语言的最后一块知识点了,以后可能会更新一些C语言的书籍的阅读,比如:《C  Primer  Plus》和《C语言深度剖析》。好啦,让我们进入程序环境和预处理的世界吧。

    2023年04月09日
    浏览(35)
  • 程序环境和预处理(下)——“C”

    各位CSDN的uu们你们好呀,今天小雅兰的内容是程序环境和预处理的下篇知识点,那么,这篇博客写完后,C语言的知识点就到这里就结束啦,后续会专注于刷题和读书,也是关于C语言的,会写一些数据结构和C++的内容,好啦,让我们进入程序环境和预处理的世界吧 预处理详解

    2023年04月16日
    浏览(30)
  • C语言--程序环境和预处理(宏)

    目录 前言 本章重点:  1. 程序的翻译环境和执行环境 2. 详解编译+链接 2.1 翻译环境​编辑 2.2 编译本身也分为几个阶段 2.3 运行环境 3. 预处理详解 3.1 预定义符号 3.2 #define 3.2.1 #define 定义标识符 3.2.2 #define 定义宏 2.2.3 #define 替换规则 3.2.4 #和##  3.2.5 带副作用的宏参数 3.2.6 宏

    2024年02月07日
    浏览(34)
  • 程序环境和预处理(含C语言程序的编译+链接)--2

    文章前言: 上章我们把      程序的翻译环境     程序的执行环境   C语言程序的编译+链接     预定义符号介绍    预处理指令   #define    宏和函数的对比     预处理操作符    #和##的介绍   的相关知识进行了梳理讲解,接下来被把剩余知识    命令定义     预处

    2024年02月14日
    浏览(42)
  • 学C的第三十四天【程序环境和预处理】

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 学C的第三十三天【C语言文件操作】_高高的胖子的博客-CSDN博客  ====================================

    2024年02月12日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包