C语言学习笔记——程序环境和预处理
目录
前言
一、程序环境
1. 翻译环境
1.1 主要过程
1.2 编译过程
2. 运行环境
二、预处理
1. 预定义符号
2. #define
2.1 #define定义标识符
2.2 #define定义宏
2.3 命名约定和移除定义
3. 条件编译
4. 文件包含
结束语
前言
每次我们写完代码运行的时候都会弹出来一个黑框框,这个黑框框实际上是一个可执行程序(.exe文件)。那么代码是如何被变成一个可执行文件的呢?其实这就是编译器所做的事,一起来了解了解吧。
一、程序环境
1. 翻译环境
1.1 主要过程
代码不可能凭空运行,只有可执行程序才能在计算机上运行。因此,在翻译环境下,代码会经过编译,链接形成一个可执行程序。如下图,组成程序的各个源文件经过编译器的编译形成各自的目标文件,再由链接器链接形成一个可执行程序。在链接过程之中,链接器会从C语言标准库中引入程序中所用到的函数。
1.2 编译过程
上述过程中的编译过程又可细分为预编译(预处理)、编译。翻译三个阶段。
预处理:这一阶段主要用来执行各种预处理指令,例如#define定义标识符常量,宏。之前在介绍枚举常量时与#define定义的标识符常量进行对比,标识符常量无法进行调试,这就是因为标识符常量在预处理时就已经被替换为常量值。
编译:编译阶段主要对代码的语法,词法,语义进行分析,检查。确保代码无语法错误后,将各个文件中的符号(函数名,全局变量等)进行汇总,便于跨文件调用。
翻译:计算机只能识别二进制的代码,因此在链接之前,编译器需要将C语言代码经汇编代码翻译为二进制代码 ,并形成目标文件。
2. 运行环境
程序在执行时必须载入内存中,这一步一般由操作系统完成。若在无操作系统的环境中,则需手动完成。程序开始执行会调用main函数,这时候需使用一个运行的堆栈用以存储函数的局部变量和地址。程序也可用静态区存储静态变量和全局变量。最后,程序正常结束(也有可能异常结束)。
二、预处理
1. 预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
以上列举的预定义符号为C语言内置的符号,使用效果如下图。

2. #define
2.1 #define定义标识符
#defien定义标识符的规则为”#define + 标识符名称 + 内容“。
#define定义的标识符会在预处理阶段直接被替换为其内容,例如下方为一段标识符的定义和使用。在预处理阶段,"MAX"被替换为"100",即将100赋值给变量max。
#define MAX 100int max = MAX;
同样地,#define可以以任何数据为内容,因此我们可以对switch语句中的case,break进行如下改造。改造的依据是C语言外的其它语言中部分语言的switch语句不需要使用break结束情况。当其它语言的程序员写C程序时可能会使用如下方式写switch语句。
#define CASE break;caseswitch(x)
{case 1:printf("%d\n",1);CASE 2:printf("%d\n",2);CASE 3:printf("%d\n",3);CASE 4:printf("%d\n",4);default:printf("%d\n",0);break;
}
这段代码在预处理阶段会将CASE替换为break;case,将各个情况分开并且不用在每种情况后手动加上break。
当然,由于标识符是直接替换内容,使用不熟练可能会造成逻辑错误,例如下方代码
#define A 3+3int a = 2*A;
很多新手会认为此时的a的值为12,因为A就是“3+3=6”嘛,那么2*A就是12。这段代码的中的a的值应该为9。由于A为标识符,因此代码中的A直接替换为标识符内容"3+3",即"int a = 2*3+3",结果为9。若想达到预期效果,则需加上括号,如下
#define A (3+3)int a = 2*A;
2.2 #define定义宏
#define定义宏的规则与标识符相似:"#define + 宏名称(宏参数) + 内容"。
宏的形式与函数相似,都需要传入参数,不同的是,宏的参数是直接替换到宏的内容中,而函数则是使用参数的值。例如下方代码
这段代码中,宏的计算为"2+1*3=5",而函数的计算为"3*3=9"。
与函数相比宏的优点:
1、 函数的调用需要在栈上开辟空间,传参,返回值,销毁空间。这些准备工作可能比实际计算所需的时间要多得多,相比之下宏在处理一些简单计算时的速度更快。
2、 另外,类似于标识符,宏的参数可以为任何内容,这是函数无法做到的,函数的参数必须是声明的指定类型的数据,因此,宏的使用较函数更加灵活,例如向宏中传入一个关键字,这是函数无法做到的。

宏的缺点:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
2.3 命名约定和移除定义
命名约定:由于宏在使用时与函十分相似,因此人们约定在定义宏名称时全部用大写字母而在定义函数名时不全用大写字母。
移除定义:使用"#undef"指令移除一个宏定义,如下图。
3. 条件编译
当有些代码需要在特定的条件下执行,其它条件下不执行时,就需要用到条件编译。条件编译类似于if分支语句,区别是条件编译指令是在预处理阶段执行,而分支语句是在函数内执行。这里列举一条常见的条件编译指令
#if 常量表达式//满足条件执行内容
#endif
这段指令就是在常量表达式为真时执行#if与#endif之间的代码。例如

第一段代码由于常量表达式为真,故定义函数func并成功调用。而第二段代码由于常量表达式为假,函数func未定义,故调用失败报错。
与if分支语句类似,#if条件编译指令也可出现分支,如下
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif
另外,这里有两组判断表达式是否被定义的条件编译指令。若"symbol"已定义,则上面两段指令的内容被执行,若未定义则下面两段指令的内容被执行。
#if defined(symbol)
#ifdef symbol#if !defined(symbol)
#ifndef symbol

4. 文件包含
当我们需要使用C语言标准库里的函数时,或是使用自己所写的头文件里的内容时,必须包含相应的头文件。包含头文件有以下两种写法(以"stdio.h"为例)
#include<stdio.h>
#include"stdio.h"
其中使用尖括号"<>"代表直接从C标准库中查找该头文件,而使用引号则代表从程序内部和C标准库中查找。因此,在我们包含C标准库中的头文件时通常使用尖括号以提高效率。
在大型项目中,多个程序员可能会多次调用同一个头文件,这样容易导致代码的运行效率降低。我们可以使用条件编译的方式来解决这个问题,如下方代码
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
这样的写法就可以避免同一个头文件的重复调用,大大提高项目的效率。另外,在头文件开头加上以下代码也可达到相同效果。
#pragma once
结束语
程序环境和预处理对于新手来说运用不多,但也算是一块比较重要的内容,有助于新手理解程序的编译形成过程,熟悉预处理操作。
相关文章:
C语言学习笔记——程序环境和预处理
目录 前言 一、程序环境 1. 翻译环境 1.1 主要过程 1.2 编译过程 2. 运行环境 二、预处理 1. 预定义符号 2. #define 2.1 #define定义标识符 2.2 #define定义宏 2.3 命名约定和移除定义 3. 条件编译 4. 文件包含 结束语 前言 每次我们写完代码运行的时候都…...
「JVM 高效并发」Java 内存模型
Amdahl 定律代替摩尔定律成为了计算机性能发展的新源动力,也是人类压榨计算机运算能力的最有力武器; 摩尔定律,描述处理器晶体管数量与运行效率之间的发展关系;Amdahl 定律,描述系统并行化与串行化的比重与系统运算加…...
C语言刷题(2)——“C”
各位CSDN的uu们你们好呀,今天小雅兰来复习一下之前所学过的内容噢,复习的方式,那当然是刷题啦,现在,就让我们进入C语言的世界吧 当然,题目还是来源于牛客网 完完全全零基础 编程语言初学训练营_在线编程题…...
第一个 Spring MVC 注解式开发案例(初学必看)
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
openresty学习笔记
openresty 简介 openresty 是一个基于 nginx 与 lua 的高性能 web 平台,其内部 集成了大量精良的 lua 库、第三方模块以及大数的依赖项。用于 方便搭建能够处理超高并发、扩展性极高的动态 web 应用、 web 服务和动态网关。 openresty 通过汇聚各种设计精良的 ngi…...
微信小程序DAY3
文章目录一、页面导航1-1、声明式导航1-2、编程式导航1-3、声明式导航传参1-4、编程式导航传参1-5、获取导航传递的参数二、页面事件2-1、下拉刷新事件2-1-1、启用下拉刷新2-1-2、配置下拉刷新2-1-3、监听页面下拉刷新事件2-2、上拉触底事件2-2-1、事件触发2-2-1、事件配置三、…...
【CAN】手把手教你学习CAN总线(一)
CAN总线一、CAN总线概念二、CAN的差分信号三、CAN总线的通信协议1、 帧起始2、仲裁段3、控制段4、数据段5、CRC段6、ACK段7、帧结束四、CAN的位时序1、同步段(SS)2、传播时间段(PTS)3、相位缓冲段(PBS)4、再…...
JUC 体系的基石——AQS
—— AQS(AbstractQueuedSynchronizer) 概念 抽象队列同步器;volatile cas 机制实现的锁模板,保证了代码的同步性和可见性,而 AQS 封装了线程阻塞等待挂起,解锁唤醒其他线程的逻辑。AQS 子类只需要根据状…...
Qt中信号与槽的使用
Qt中信号与槽的使用 Qt当中一个重要的东西是信号和槽,它被用于对象之间的通信。 在Qt中,例如“点击按钮”这个事件就是发送信号的对象,接收信号的是某一个窗口,响应信号的是一个处理,可以是隐藏窗口或者是关闭窗口。…...
力扣-销售员
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:607. 销售员二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …...
HTML综合案例练习
一、展示简历内容 可以首先看一下我们的效果,之后再思考怎么实现 总的来说,这个练习不算难。 这里关于这个简历的代码编写我们不说太多,只注意以下几个内容即可: 注意及时查看我们的代码是否符合预期,即一段一段测 …...
MySQL运维
目录 1、日志 1、错误日志 2、二进制日志 3、查询日志 4、慢查询日志 2、主从复制 搭建 1、主库配置 2、从库配置 3、分库分表 1、简介 编辑 1、垂直拆分 2、水平拆分 3、实现技术 2、MyCat 3、MyCat使用和配置 配置 4、MyCat分片 1、垂直拆分 2、水平拆分…...
【网络原理10】构造HTTP请求、HTTPS加密
目录 一、构造HTTP请求 ①使用form表单构造HTTP请求: form表单是如何提交的 form提交的缺点 ②基于ajax构造http请求 如何使用Jquery框架 二、HTTPS 运营商劫持 HTTP的加密版本:HTTPS ①对称加密:客户端和服务端使用同一把密钥&…...
Allegro如何锁定报表界面操作指导
Allegro如何锁定报表界面操作指导 用Allegro做PCB设计的时候,进行测量的时候,比如测量器件两个PIN中间的间距,如下图,会有一个报表显示 但是当运行下一个命令的时候,报表会被自动关闭掉。 但是有时我们需要报表界面仍被保留 下面介绍如何将报表界面进行锁定,不受下一个…...
基于STM32的微型电子琴设计
基于STM32的微型电子琴设计报告中的图片和文字太多了,全部一个一个把搬过来太麻烦了,需要完整文本和代码自行q我963160156 第一章 总体设计1.1 系统功能1.2 主要技术性能指标第二章硬件设计2.1 整体硬件图2.2 按键模块2.3 扬声器模块2.4 显示模块2.5 主控模块第三章…...
Shell输入输出重定向
一、文件描述符 文件描述符是一个非负整数。它是一个索引值,指向进程打开的文件。 Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。 每个文件描述符会与一个打开的文件相对应 不同的文件描述符也可能指向同一个文件 在L…...
华为OD机试-运维日志排序
文章目录题目描述输入描述输出描述:示例Java 代码实现题目描述 运维工程师采集到某产品线网运行一天产生的日志n条,现需根据日志时间先后顺序对日志进行排序,日志时间格式为H:M:S.N。 H表示小时(0~23) M表示分钟(0~59) S表示秒(0~59) N表…...
1Kotlin基础知识
1 变量 1.1 用法 Kotlin中的变量定义有2个关键字,val和var val用来定义不可变变量,第一次赋值后就不能再被修改了, var定义可变变量, 随便修改。一个好的编程习惯是, 能用val的就不要用var, 原因是安全&a…...
Redis Lua脚本
文章目录一.引言二.eval简介三.lua数据类型和redis数据类型之间转换四.脚本的原子性五.错误处理六.纯函数脚本七.选择内部脚本一.引言 eval和evalsha命令使用内置的lua解释器,可以对lua脚本进行求值。 二.eval简介 第一个参数是一段脚本程序第二个参数是参数的个…...
web自动化测试-执行 JavaScript 脚本
JavaScript 是一种脚本语言,有的场景需要使用 js 脚本注入辅助我们完成 Selenium 无法做到的事情。 当 webdriver 遇到无法完成的操作时,可以使用 JavaScript 来完成,webdriver 提供了 execute_script() 方法来调用 js 代码。 执行 js 有两种…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
