C进阶:7.程序环境和预处理

news2025/7/28 2:19:35

目录

1.程序的翻译环境和执行环境

2.详解编译 + 链接

2.1翻译环境

2.2编译本身也分为几个阶段:

2.3运行环境

3.预处理详解

3.1预定义符号

3.2 #define

3.2.1 #define 定义标识符

3.2.2 #define 定义宏

3.2.3 #define 替换规则

3.2.4  # 和 ##

3.2.5带副作用的宏参数

3.2.6宏和函数对比

3.2.7命名约定

3.3#undef

3.4命令行定义

3.5条件编译

3.6文件包含

3.6.1 头文件被包含的方式

3.6.2嵌套文件包含

4.其他预处理指令


本章重点:

  • 程序的翻译环境
  • 程序的执行环境
  • 详解:C语言程序的编译 + 链接 
  • 预定义符号介绍
  • 预处理指令 #define
  • 宏和函数的对比
  • 预处理操作符 # 和 ## 的介绍
  • 命令定义
  • 预处理指令 #include
  • 预处理指令 #undef
  • 条件编译

1.程序的翻译环境和执行环境

在ANSIC的任何一种实现中,存在两个不同的环境。

第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令

第二种是执行环境,它用于实际执行代码。

2.详解编译 + 链接

2.1翻译环境

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

2.2编译本身也分为几个阶段:

sum.c

int g_val = 2016;
void print(const char* str)
{
    printf("%s\n",str);
}

test.c

#include <stdio.h>
int main()
{
    extern void print(char* str);
    extern int g_val;
    printf("%d\n",g_val);
    print("hello bit.\n");
    return 0;
}

如何查看编译期间的每一步发生了什么?

  1. 预处理 选项 gcc -E test.c -o test.i  ,预处理完成之后产生的结果都放在test.i文件中。
  2. 编译选项 gcc -S test.c ,编译完成之后就停下来,结果保存在test.s。
  3. 汇编 gcc -c test.c ,汇编完成之后就停下来,结果保存在test.o中。

2.3运行环境

程序执行的过程:

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

3.预处理详解

3.1预定义符号

__FILE__     //运行编译的源文件
__LINE__     //文件当前的行号
__DATE__     //文件被编译的日期
__TIME__     //文件被编译的时间
__STDC__     //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的。

举个栗子:

printf("file:%s line:%d\n",__FILE__,__LINE__);

3.2 #define

3.2.1 #define 定义标识符

//语法
#define name stuff

举个栗子:

#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现  死循环
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                        date:%s\ttime:%s\n" ,\
                        __FILE__,__LINE__ , \
                        __DATE__,__TIME__ )

注意:在define 定义标识符的时候,不要在后面加上;。

举个栗子:

#define MAX 1000;
#define MAX 1000

建议不要加上;,这样容易导致问题。

比如下面的场景:

if(condition)
    max = MAX;  //max = 1000;;
else
    max = 0;

由于上述会形成 max = 1000;; 形成2个语句,会导致else找不到最近的if。

3.2.2 #define 定义宏

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

下面是宏的申明方式:

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

注意:

  • 参数列表的左括号必须与name紧邻。
  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

举个栗子:

#define SQUARE(x) x*x

这个宏接受一个参数 x ,如果在上述声明之后,你把 

SQUARE(5);

 置于程序中,预处理就会用下面的这个表达式替换上表面的表达式。

5*5

警告:这个宏存在一个问题。观察下面的代码:

int a = 5;
printf("%d\n",SQUARE(a + 1));

乍一看可能会觉得这段代码会打印 36 这个值。

事实上,它将打印11。为什么呢?

预处理替换文本时,参数x被替换成 a+1 ,所以这条语句实际上变成了:printf("%d\n",a+1*a+1)

所以替换产生的表达式并没有按照预想的次序进行求职。

在宏定义上加上两个括号,这个问题便轻松解决了:

#define SQUARE(x) (x)*(x)

这样在预处理后就产生了预期的效果:

printf("%d\n",(a+1)*(a+1));

举个栗子:

#define DOUBLE(x) (x)+(x)

定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。

int a = 5;
printf("%d\n",10*DOUBLE(a));

警告:看上去,好像打印了100,但是实际上打印的是55。因为在预处理之后:

printf("%d\n",10*(5)+(5));

乘法运算优先于宏定义的加法,所以出现了 55;

这个问题的解决方法是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE(x) ((x)+(x))

提示:

所有对于数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

3.2.3 #define 替换规则

在程序中扩展#define 定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含 #define 定义的符号。如果是,他们首先被替换。
  2. 替换文本随后被插入到程序中原来的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看她是否包含任何由#define定义的符号。如果是,就重复以上的处理过程。

注意:

  1. 宏参数和#define定义可以出现其他 #define 定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索 #define 定义的富豪的时候,字符串常量的内容并不被搜索。

3.2.4  # 和 ##

如何把参数插入字符串中

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

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

在这里输出的是hello bit

我们发现字符串是有自动连接的特点的。

那我们是不是可以这样写代码?

#define PRINT(FORMAT, VALUE) printf("the value is "FORMA"\n",VALUE);
PRINT("%d",10);

 这是只有当字符串作为宏参数的时候才可以把字符串放在字符串中。

1.使用 # ,把一个宏参数变成对应的字符串。

比如:

//int i = 0;
#define PRINT(FORMAT,VALUE) printf("the vlaue of " #VALUE"is FORMAT"\n",VALUE);

PRINT("%d",i+3);//产生了什么效果?

注:代码中的#VALUE 会预处理为:“VALUE”.

最终输出的结果是:

the value of i+3 is 13

2.##的作用

##可以把位于它两边的符号合成一个符号。

它允许宏定义从分离的文本片段创建标识符。

#define ADD_TO_SUM(num,value) sum##num += value;
...
ADD_TO_SUM(5,10);//作用是:给sum5增加10

注:这样的连接必须产生一个合法的标识符,否则其结果就是未定义的。

一个小栗子:

#define CAT(x,y) x##y
//Class##109
//Class109
int main()
{
    int Class109 = 2023;
    printf("%d\n",CAT(Class,109));//连接两个字符串。
}

3.2.5带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的结果。副作用就是表达式求值的时候出现的永久性效果。

例如:

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题。

#define MAX(a,b) ((a)>(b)?(a):(b))
...
x = 5;
y = 8;
z = MAX(x++,y++);
printf("x=%d y=%d z=%d\n",x,y,z);//输出的结果是什么?

这里我们得知道预处理之后的结果是什么:

z = ((x++) > (y++) ? (x++) : (y++));

所以输出的结果是:

x = 6 y = 10 z = 9   

3.2.6宏和函数对比

宏通常被应用于执行简单的运算,比如在两个数中找到较大的一个。

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

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

宏的优点

  1. 用于调用函数和从函数返回地代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型适合的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点数等可以用来比较的类型。                     宏是类型无关的。 

宏的缺点:当然和函数相比宏也有劣势的地方

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,导致程序容易出错。

宏有时候可以做到函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num,type) (type*)malloc(num* sizeof(type))
...
//使用
MALLOC(10,int);//类型作为参数

//预处理替换之后。
(int*)malloc(10 * sizeof(int));

宏和函数的一个对比

3.2.7命名约定

一般来讲函数的宏的使用语法很相似,所以语言本身没法帮我们区分二者。那我们平时的习惯是:

把宏全部大写

函数名不要全大写

3.3#undef

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

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

3.4命令行定义

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

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

#include <stdio.h>
int main()
{
    int array[ARRAY_SIZE];
    int i = 0;
    for(i=0;i<ARRAY_SIZE;i++)
    {
        array[i] = i;
    }
    for(i=0;i<ARRAY_SIZE;i++)
    {
    printf("%d",array[i]);
    }
    printf("\n");
    return 0;
}

编译指令:

//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++)
    {
        #ifdef __DEBUG__
        printf("%d\n",arr[i]);//为了观察数组是否赋值成功
        #endif  //__DEBUG__
    }
    return 0;
    
}

常见的条件编译指令:

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

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

3.判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol

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

3.6文件包含

#include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。

这种替换的方式很简单:预处理先删除这条指令,并用包含的文件替换。

这样一个源文件被包含10次,那就实际被编译10次。

3.6.1 头文件被包含的方式

  • 本地文件包含
#include "filename"

查找策略:先在源文件目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就是编译错误。 

Linux环境的标准头文件的路径:

/usr/include

VS环境的标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是vs2013的默认路径

注意按照自己的安装路径去找。

  • 库文件包含
#include <filename.h>

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

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?答案是肯定的,可以。

但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件。

3.6.2嵌套文件包含

如果出现这样的场景

comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。

如何解决这个问题----------条件编译

每个头文件的开头写:

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

或者:

#pragma once

就可以避免头文件的重复引入。

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

笔试题:

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

        防止头文件被重复编译

2.#include <filename.h> 和#include “filename.h”有什么区别?

        #include <filename.h>直接去库里去查找,比较直接。#include “filename.h”会首先去工程目录下查找,如果找不到,再去库里查找。

4.其他预处理指令

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/368096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大规模食品图像识别:T-PAMI 2023论文解读

美团基础研发平台视觉智能部与中科院计算所展开科研课题合作&#xff0c;共同构建大规模数据集Food2K&#xff0c;并提出渐进式区域增强网络用于食品图像识别&#xff0c;相关研究成果已发表于T-PAMI 2023。本文主要介绍了数据集特点、方法设计、性能对比&#xff0c;以及基于该…

Unreal Engine 虚幻引擎,性能分析,优化(二)

一、CPU 性能分析 如渲染线程中出现 CPU 受限&#xff0c;原因可能是绘制调用过多。这是一个常见问题&#xff0c;美术师通常会将绘制调用进行组合&#xff0c;从而减少消耗&#xff08;如&#xff1a;将多个墙壁组合为一个网格体&#xff09;。实际消耗存在于多个区域中&…

Ubuntu 22.04.2 发布,可升级至 Linux Kernel 5.19

Ubuntu 22.04 LTS (Jammy Jellyfish) Ubuntu 22.04.2 发布&#xff0c;可升级至 Linux Kernel 5.19 请访问原文链接&#xff1a;Ubuntu 22.04 LTS (Jammy Jellyfish)&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;www.sysin.org 发行说…

file_get_contents 打开本地文件报错: failed to open stream: No such file or directory

php 使用file_get_contents时报错 failed to open stream: No such file or directory (打开流失败&#xff0c;没有这样的文件或目录) 1. 首先确保文件路径没问题 最好是直接复制一下文件的路径 2. windows电脑可以右键该文件 → 属性→安全 →对象名称 选中后复制一下 3. 然后…

【数据存储】浮点型在内存中的存储

目录 一、存储现象 二、IEEE标准规范 1.存储 2.读取 三、举例验证 1.存储 2.读取 浮点型存储的标准是IEEE&#xff08;电气电子工程师学会&#xff09;754制定的。 一、存储现象 浮点数由于其有小数点的特殊性&#xff0c;有很多浮点数是不能精确存储的&#xff0c;如&#…

【unittest学习】unittest框架主要功能

1.认识unittest在 Python 中有诸多单元测试框架&#xff0c;如 doctest、unittest、pytest、nose 等&#xff0c;Python 2.1 及其以后的版本已经将 unittest 作为一个标准模块放入 Python 开发包中。2.认识单元测试不用单元测试框架能写单元测试吗&#xff1f;答案是肯定的。单…

UNIAPP实战项目笔记58 注册成功信息存储到数据库

UNIAPP实战项目笔记58 注册成功信息存储到数据库 注册时候验证手机验证码 验证码通过后讲用户信息存入数据库 实际案例图片 后端接口文件 index.js var express require(express); var router express.Router(); var connection require(../db/sql.js); var user require(…

爱奇艺“资产重定价”:首次全年运营盈利是拐点,底层逻辑大改善

长视频行业历时一年有余的降本增效、去肥增瘦&#xff0c;迎来首个全周期圆满收官的玩家。 北京时间2月22日美股盘前&#xff0c;爱奇艺发布2022年Q4及全年财报&#xff0c;Q4 Non-GAAP净利润明显超越预期&#xff0c;且首次实现全年运营盈利。受业绩提振&#xff0c;爱奇艺盘…

SpringSecurity源码分析(二) SpringBoot集成SpringSecurity即Spring安全框架的执行过程

在上一篇文章中我们描述了SpringSecurity的执行过程。我们我们了解到了以下内容 在SpringSecurity框架中有三个非常核心的类和接口&#xff0c;分别是 1.SecurityFilterChain接口 2.FilterChainProxy类 3.DelegatingFilterProxy类 springboot项目中&#xff0c;客户端向Tomcat …

day51【代码随想录】动态规划之回文子串、最长回文子序列

文章目录前言一、回文子串&#xff08;力扣647&#xff09;二、最长回文子序列&#xff08;力扣516&#xff09;前言 1、回文子串 2、最长回文子序列 一、回文子串&#xff08;力扣647&#xff09; 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目…

从网易到支付宝,3年外包生涯做完,我这人生算是彻底废了......

我为什么一直做外包呢&#xff0c;原因是薪资和技术方面。 在华三做了一年外包&#xff0c;薪资5k&#xff0c;功能测试&#xff0c;接触Linux和网络&#xff0c;但是说实在的技术很难沉淀&#xff0c;就像雾里看花一样&#xff0c;过年之后&#xff0c;想走的人都走了&#x…

Word处理控件Aspose.Words功能演示:使用 C# VB.NET 将 DOCX 转换为 DOC 或将 DOC 转换为 DOCX

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

深度学习编译器CINN(1):框架概览和编译安装

目录 框架概览 编译安装 参考 框架概览 CINN是一种在不改变模型代码的条件下加速飞桨模型运行速度的深度学习编译器。CINN致力于创造训推一体自动调优、分布式编译加速等特色功能&#xff0c;对深度学习模型提供全自动、极致的性能优化&#xff0c;并在科研界和工业界建立影…

【布隆过滤器(Bloom Filter)基本概念与原理、Bloom Filter优点与缺点、以及应用场景】

布隆过滤器&#xff08;Bloom Filter&#xff09;基本概念与原理、Bloom Filter优点与缺点、以及应用场景 Bloom Filter 基本概念 布隆过滤器是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在…

DualCor: Event Causality Extraction with Event Argument Correlations论文解读

Event Causality Extraction with Event Argument Correlations(带有事件论元相关性的事件因果关系抽取) 论文&#xff1a;2301.11621.pdf (arxiv.org) 代码&#xff1a;cuishiyao96/ECE: Dataset and code for COLING2022 paper Event Causality Extraction with Event Argum…

【vue 10 第一个vue-cli程序】

1.什么是vue-cli vue-cli官方提供的一个脚手架&#xff0c;用于快速生成一个vue的项目模版&#xff1b; 预先定义好的目录结构及基础代码&#xff0c;就好比咱们在创建Maven项目时可以选择创建一个骨架项目&#xff0c;这个骨架项目就是脚手架&#xff0c;我们的开发更加的快速…

FC总线知识点小结

FC总线 &#xff08;一&#xff09;特点&#xff1a; FC具备通道和网络双重优势&#xff0c;具备高带宽、高可靠性、高稳定性&#xff0c;抵抗电磁干扰等优点&#xff0c;能够提供非常稳定可靠的光纤连接&#xff0c;容易构建大型的数据传输和通信网络&#xff0c;目前支持1x…

2022阿里,字节跳动,JAVA岗(一线企业校招、社招)面试题合集

前言 以下面试题全属于一线大厂社招以及校招的面试真题&#xff0c;各位在做这些题目对照自己的时候请平凡心对待&#xff0c;不要信心受挫。其实 做为致力于一线企业校招或者社招的你来说&#xff0c;能把每个知识模块的一小部分问题去深入学习和总结&#xff0c;已经很棒了&…

SpringBoot整合阿里云OSS文件上传、下载、查看、删除

SpringBoot整合阿里云OSS文件上传、下载、查看、删除1、开发准备1.1 前置知识1.2 环境参数1.3 你能学到什么2. 使用阿里云OSS2.1 创建Bucket2.2 管理文件2.3 阿里云OSS文档3. 项目初始化3.1 创建SpringBoot项目3.2 Maven依赖3.3 安装lombok插件4. 后端服务编写4.1 阿里云OSS配置…

Ask林曦|来回答,30个你关心的日常问题(三)

在林曦老师的线上书法直播课上&#xff0c;上课前后的聊天时间里&#xff0c;时常有同学向林曦老师提问&#xff0c;这些问题涵盖了日常生活的诸多方面&#xff0c;从身体的保养&#xff0c;到快乐的法门&#xff0c;皆是大家感兴趣的&#xff0c;也都共同关切的。      暄…