当前位置: 首页 > news >正文

C/C++:程序环境和预处理/宏

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

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

编译和链接

一份源代码(比如test.c)需要通过编译,形成一份目标文件,然后与库连接起来,才能形成一份可执行程序test.exe。

编译的过程

编译的过程为:预处理(预编译)、编译、汇编。

预处理:在预处理阶段,源文件包含的头文件会被展开,注释会被去掉,宏会进行替换等等。注意此时还不算是运行了程序,因为还没形成可执行程序。

编译:在编译阶段会把C语言、C++语言等等翻译成汇编语言,会进行语法分析,词法分析,符号总汇,语义分析。其中的符号总汇,是把全局变量,函数名称总汇。

汇编:把汇编代码转化成二进制指令,形成符号表。符号表里面是函数名称和其对应的地址,如果该函数没有被定义,则会给一个无效地址。

链接

在此阶段,会合并段表,进行符号表的合并和重定位,将所有涉及的库链接起来。符号表的合并的作用是能够找到需要的函数、全局变量等等。

编译源文件的测试,我们可以在gcc下进行:

1. 预处理 选项 gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。

2. 编译 选项 gcc -S test.c
编译完成之后就停下来,结果保存在test.s中。

3. 汇编 gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中

程序的运行环境

程序执行的过程:

1. 程序必须先载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成

2. 程序的执行便开始。接着便调用main函数。

3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4. 终止程序。正常终止main函数;也有可能是意外终止

预处理

预定义符号

__FILE__
__LINE__
__DATE__
__TIME__
__STDC__

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

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

#define

使用#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定义宏

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

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

注意:

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

②在定义宏的时候,不要吝啬括号。

#include<stdio.h>
#define SQUARE(x) x*x  //(5+1) r = 5+1*5+1 == 11
#define SQUARE(x) (x)*(x) // (5+1)*(5+1) = 6*6 = 36
#define DOUBLE(x) (x)+(x)  //k == 6  s == 33
#define DOUBLE(x) ((x)+(x))//k == 60int main()
{int r = SQUARE(5+1);//宏是替换,不是计算再替换过去printf("%d\n", r);int k = DOUBLE(3);//6//这里需要括起来,因此,最好外面再括号一下int s = 10 * DOUBLE(3);//如果不括号 s = 10*(3)+(3)return 0;
}

#define替换规则

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

①在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

②替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

③最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

注意:

①宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

②当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

#define PRINT(N,X) printf("the value of "#N" is "#X"\n",N)
//#N,就是将a或b,转换成“a” “b”int main()
{int a = 2;PRINT(a,"%d");double b = 2.5;PRINT(b,"%.1lf");return 0;
}

 

##

##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符

利用##,我们可以将参数插入到字符串当中。

#define CAT(Class,num) Class##num
int main()
{int Class106 = 100;printf("%d\n", CAT(Class, 106));return 0;
}

 带副作用的宏参数

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

//有副作用的宏参数//什么副作用?
int main()
{int a = 10;int b = a + 1;int c = ++a;//副作用:导致a的值也变了return 0;
}#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{//int m = MAX(2, 3);int a = 5;int b = 4;int m = MAX(a++, b++);//   ((a++)>(b++)?(a++):(b++))//(5,4) ((5,使用后++,变6)>(4,使用后++,变为5)?(6,使用后++,7):(b,没使用))printf("%d\n", m);//6printf("%d\n", a);//7printf("%d\n", b);//5return 0;
}

宏和函数对比

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

//宏
#define MAX(a,b) ((a)>(b)?(a):(b))
//函数
int MAX(int a, int b)
{return (a > b ? a : b);
}

其实对于这样简单的任务,用宏来进行,会比使用函数的效率高。

原因有二:

①用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。

②更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

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

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
//使用宏MALLOC,灵活地使用不同的类型
MALLOC(10, int);//类型作为参数
MALLOC(10, double);//类型作为参数
//预处理器替换之后:
(int*)malloc(10 * sizeof(int));
(double*)malloc(10 * sizeof(double));//原本的malloc的使用,需要分开写
(int*)malloc(10 * sizeof(int)); 
(double*)malloc(10 * sizeof(double));

宏相对函数的缺点:

① 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

②宏是没法调试的。

③宏由于类型无关,也就不够严谨。这一点,是宏的一把双刀刃,即使优点也是缺点。

④宏可能会带来运算符优先级的问题,导致程容易出现错。因此,不能吝啬括号。

总结宏和函数的对比:

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

宏命名的约定:

一般来说,一般都是英文全大写来命名宏。不过也有会采用小写,我们需要懂得分辨。

#undef

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

#define MAX 100
int main()
{printf("%d\n", MAX);
#undef MAXprintf("%d\n", MAX);//MAX被移除了,这里报错return 0;
}

条件编译

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

#include <stdio.h>
#define __DEBUG__  //当把这条宏定义注释掉,那么就不会执行printf
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;
}

常见的条件编译指令:

#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

文件包含

 #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。这种替换的方式很简单,那就是预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。很显然,这样是很不好的,如果不小心包含了多个同样的头文件,每个头文件里面有几千行代码,那么重复的代码就会非常的多。

因此,我们可以在头文件中加入条件编译:

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

或者是:#pragma once

这样一来,就不会编译多次同样的源文件了。

对于头文件来说,有以下两种形式:

①#include <......>  :以<>来包含头文件名的,直接去标准路径下查找头文件、

②#include "......"    以""来包含头文件名的,先是去源文件的路径下寻找,找不到再去标准路径中找。这种效率比较低。

相关文章:

C/C++:程序环境和预处理/宏

程序的翻译环境和执行环境 在ANSI C的任何一种实现中&#xff0c;存在两个不同的环境。第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境&#xff0c;它用于实际执行代码。 编译和链接 一份源代码(比如test.c)需要通过编译&#xf…...

什么是死锁?死锁产生的四个必要条件是啥?如何避免和预防死锁的产生?

点个关注&#xff0c;必回关 文章目录什么是死锁死锁产生的原因&#xff1a;1、系统资源的竞争2、进程运行推进顺序不当引起死锁产生死锁的四个必要条件&#xff1a;死锁的避免与预防什么是死锁 死锁是指两个或两个以上的线程在执行过程中&#xff0c;由于竞争资源或者由于彼此…...

工程管理系统源码-物料管理-工程项目管理系统-建筑施工管理软件

如今建筑行业竞争激烈&#xff0c;内卷严重&#xff0c;发展趋势呈现两极分化&#xff0c;中小微企业的生存空间被逐步压缩&#xff0c;利润逐年减少。事实证明&#xff0c;工地上粗放式的人管人管理模式已经落于时代&#xff0c;劳动力纠纷和物料浪费现象尤其普遍&#xff0c;…...

Roboguide与TIA V16通讯

软件需求:1. roboguide;2. TIA V16;3. KEPServer; 在之前的文章中介绍过KEPServer与TIA V16的通讯,此处不再介绍。接下来,介绍roboguide与KEPServer的仿真通讯。 创建一个roboguide项目。选择【外部设备】➡【添加外部设备】 选择【OPC Server】➡【OK】 OPC服务器名称命…...

利用PyTorch深度学习框架进行多元回归

目录前言数据加载数据转换模型定义模型训练模型评估模型保存与加载完整代码讨论参考文献前言 大多数数据科学家以往经常常是利用传统的机器学习框架sklearn搭建多元回归模型&#xff0c;随着深度学习和强化学习越来越普及&#xff0c;很多数据科学家尝试使用深度学习框架来进行…...

EBS常用接口开发

整理了一些工作中常用的Oracle EBS接口和API&#xff0c;最早是看着大神黄建华文档起来的&#xff0c;格式内容参考他的文档&#xff0c;加上一些自己开发的程序和经历而已。 PO PO接收、检验、入库、退货-InterfaceAPI_刘文钊1的博客-CSDN博客 基础数据 EBS物料、bom、工艺导入…...

【完整】UR机械臂逆运动学求解过程及c++代码实现

有任何问题请在评论区留言&#xff0c;我尽可能的回复大家 一. 逆运动学的求解需要以下数学运算 利用DH参数得到每个关节的变换矩阵&#xff1b;利用变换矩阵求出机械臂整个链的变换矩阵&#xff1b;求出末端位姿&#xff1b;利用已知末端位姿和整个链的变换矩阵&#xff0c;…...

68. Python的相对路径

68. Python的相对路径 文章目录68. Python的相对路径1. 知识回顾2. 什么是相对路径3. 相对路径的语法4. 查看相对路径的方法5. 写出所有txt文件的相对路径5.1 同目录5.2 上级目录6. 用相对路径读取txt文件6.1 读取旅游.txt6.2 读取旅游经费.txt6.3 读取笔记.txt和new.txt6.4 读…...

java数据类型

数据类型 类型分类&#xff0c;存储范围&#xff0c;字面量&#xff0c;默认值&#xff0c;类型转换 类型分类 存储范围 数据类型字节数表示范围byte1-128~127short2-32768~32767&#xff0c;正负3万左右int4-2147483648~2147483647&#xff0c;正负21亿左右long8-922337203…...

Kotlin 替换非空断言的几种方式

Kotlin 出现断言的两种情形 IDE java 与 kotlin 自动转换时&#xff0c;自动添加非空断言的代码Smart Cast 失效 代码展示&#xff1a; class JavaConvertExample {private var name: String? nullfun init() {name ""}fun foo() {name null;}fun test() {if (…...

2023年了,来试试前端格式化工具

在大前端时代&#xff0c;前端的各种工具链穷出不断&#xff0c;有eslint, prettier, husky, commitlint 等, 东西太多有的时候也是trouble&#x1f602;&#x1f602;&#x1f602;,怎么正确的使用这个是每一个前端开发者都需要掌握的内容&#xff0c;请上车&#x1f697;&…...

spring cloud 企业工程项目管理系统源码+项目模块功能清单

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…...

TCP分片解析

本文目录什么是IP分片为什么会产生IP分片为什么要避免IP分片如何避免IP分片什么是IP分片 IP协议栈将TCP/UDP传输层要求它发送的&#xff0c;但长度大于发送端口MTU的一个数据包&#xff0c;分割成多个IP报文后分多次发送。这些分成多次发送的多个IP报文就是IP分片。 为什么会…...

开发了一款基于 Flask 框架的在线电影网站系统(附 Python 源码)

文章目录前言项目介绍源码获取运行环境安装依赖库项目截图首页展示图视频展示页视频播放页后台管理页整体架构设计图项目目录结构图前台功能模块图后台功能模块图本地运行图前言 今天我给大家分享的是基于 Python 的 Flask 框架开发的在线电影网站系统&#xff0c;大家平时需要…...

如何获得CSM--敏捷教练证书

1、什么是CSM&#xff1f;CSM即Certified Scrum Master,Scrum Master负责确保所有人都能正确地理解并实施Scrum&#xff0c;确保Scrum团队遵循Scrum的理论、实践和规则。Scrum Master是Scrum团队中的服务型领导&#xff0c;帮助Scrum团队外的人员了解他们如何与Scrum团队交互是…...

Java面试数据库

目录 一、关系型数据库 数据库权限 表设计及创建 表数据相关 数据库架构优化 二、非关系型数据库 redis 今天给大家稍微整理了一下&#xff0c;内容有数据表设计的三大范式原则、sql查询如何优化、redis数据的击穿、穿透、雪崩等...&#xff0c;以及相关的面试题&#xff0…...

关于进行vue-cli过程中的解决错误的问题

好久没发文章了&#xff0c;直到今天终于开始更新了&#xff0c;最近想进军全端&#xff0c;准备学习下vue&#xff0c;但是这东西真的太难了&#xff0c;我用了一天的时间来解决在配置中遇到的问题&#xff01;主要问题&#xff1a;cnpm文件夹和vue-cli文件夹的位置不对并且vu…...

Rockchip Linux USB Gadget

一:概述 USB Gadget 是运行在 USB Peripheral 上配置 USB 功能的子系统,正常可被枚举的 USB 设备至少有 3 层逻辑层,有些功能还会在用户空间多跑一层逻辑代码。Gadget API 就是具体功能和硬件底层交互的中间层。从上到下,逻辑层分布为: USB Controller: USB上最底层的软…...

Linux -文件系统操作与帮助命令

1、Linux -文件系统操作 df — 查看磁盘的容量 df -h —以人类可以看懂的方式显示磁盘的容量&#xff0c;易读 du 命令查看目录的容量 # 默认同样以块的大小展示 du # 加上 -h 参数&#xff0c;以更易读的方式展示 du -h-d 参数指定查看目录的深度&#xff1a; # 只查看 1…...

UMI 创建react目录介绍及配置

UMI 生成react项目目录介绍及配置 react项目目录介绍umi多种配置方案运行时配置app.ts 的使用 1、umi创建的项目目录大致如下 ├─package.json 配置依赖以及启动打包所需的命令 ├─.umirc.ts 配置文件&#xff0c;包含 umi 内置功能和插件的配置 ├── dist 打包后生成的…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...