C程序环境及预处理

news2025/1/20 1:57:34

​​​​​文章目录

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

1.程序编译过程

2.编译内部原理

3.执行环境

 二、程序运行前的预处理

1.预定义符号归纳

2.define定义标识符

3.define定义宏

4.define替换规则

5.宏和函数的对比

三、头文件被包含的方式 

四、练习:写一个宏,可以将一个整数二进制位的奇偶位交换


  大家好,我是纪宁。

  在ANSI C的任何一种实现中,都存在两个不同的环境。第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。翻译环境中也有很多的内部原理,一起来学习吧。

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

1.程序编译过程

  

  其中,源文件是文件名后缀为 .c 的文件,目标文件是文件后缀名为 .obj 的文件。

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

2.编译内部原理

  在预处理阶段,系统会自动处理掉所有的预编译指令。 包括删去注释,将头文件包含,#define 定义符号和宏的替换等等

  而在编译阶段,系统会将C语言代码翻译成汇编指令,还有语法分析、词义分析、语义分析、符号汇总等

  在汇编阶段。系统将汇编指令翻译成二进制指令,并生成.o目标文件,形成符号表。

  最后在链接阶段合并段表,重定位和合成符号表(在链接阶段可发现函数是否正常定义

3.执行环境

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

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

  最后就是终止程序。正常终止main函数,也有可能是意外终止。

 二、程序运行前的预处理

1.预定义符号归纳

  下面的这些符号都是C语言内置符号

__FILE__ 进行编译的源文件
__LINE__ 文件当前的行号
__DATE__ 文件被编译的日期
__TIME__文件被编译的时间

程序中使用对应的符号在预处理阶段自动转化为对应的含义,测试代码如下

#include<stdio.h>
int main() {
    printf("%d\n", __LINE__);
    printf("%s\n", __TIME__);
    printf("%s\n", __DATE__);
    printf("%s\n", __FILE__);
    return 0;
}

运行结果

  由于_LINE_在第四行,所以转化结果为4;时间和日期就是我测试代码的时间、日期;_FILE_则对应我源文件的路径。

2.define定义标识符

语法如下:

#define name stuff

  在预处理阶段,编译器会自动将代码中的 name 全部替换为 stuff 。但需注意在写#define定义标识符的时候,后面不能加 ;,预处理阶段编译器会将 ; 也当成要替换的内容。

3.define定义宏

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

#define name( parament-list ) stuff

  其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。它的使用方法与函数类似,只不过将函数传参改成了直接替换。因为改成了直接替换,所以宏的参数尽量加括号修饰。其次,参数列表的左括号必须与name紧邻,因为如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

  宏的参数一般会被多次替换,所以不能使用那些带 ‘副作用’ 的参数,副作用即表达式求值中出现的永久性效果,如自加自减这些运算。

  例如,求一下如下代码的运算结果:

​#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);

  大部分人可能会理所当然的以为传过去的是x++和y++表达式的值,那么结构就应该是z的结果剧应该是8,x的结果应该是6,y的结果应该是9 ?可实际上并非如此:

  这就是参数带有副作用的影响 

4.define替换规则

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

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

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

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

还有几点需要注意

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

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

5.宏和函数的对比

  在某些方面来讲,宏是非常有优势的。比如求两个数的最大值这样一个简单的工作,如果用函数来完成的话,可能会非常耗费时间,因为每次调用函数都要进行函数栈帧创建和销毁,而宏却不需要;函数传参必须是特定的类型,而宏的参数却可以在算数运算范围内传多种类型的参数。总结就是:在小型工程中,宏比函数在程序的规模和速度方面更胜一筹宏是类型无关的,且可以将类型当参数传过过去,这点是函数做不到的

  但宏也有以下缺点:

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

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 

三、头文件被包含的方式 

  在C语言中,包含头文件有两种方法:" "包含和< >包含。

  "  "包含是编译器包含本地文件的方法,查找策略是先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就提示编译错误

  而< >包含的查找头文件是直接去标准路径下去查找,如果找不到就提示编译错误。 

  看了这种情况,有人可能会问:那以后能不能包含所有文件都用 " " 来包含,答案是可以的,但是如果考虑到程序的效率问题,就另当别论了,因为 " "去查标准路径的头文件是需要查找两次的,而<>查找标准路径的头文件只需要查找一次,效率明显高很多

  为了防止头文件被重复引用,头文件中都会有条件编译的内容来防止文件内容的重复

在每个头文件的开头写这几条语句

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

或者:

#pragma once

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

这是一些常用的条件编译指令

#if
#elif
#endif

四、练习:写一个宏,可以将一个整数二进制位的奇偶位交换

  思路:将这个数的二进制位的奇数位取出,向左移动一位;将这个数的二进制位的偶数位取出,向右移动一位,最后相加即可。

  以8位二进制为例:&0x55可以得到它的奇数位,&0xaa可以得到它的偶数位,如果上升到32位二进制,那么&0x55555555可以得到它的奇数位,然后左移,&0xaaaaaaaa可以得到它的偶数位,然后右移,最后再相加即可将这个数的二进制位的奇偶位交换。

  如果看懂图解的话,写代码用宏实现就很简单了。

#include<stdio.h>
#define SWAP(n)  (n=((n&0xaaaaaaaa)>>1)+((n&0x55555555)<<1))
int main()
{
	int n = -120;
	SWAP(n);//交换奇偶位
	printf("%d\n", n);
	SWAP(n);//再交换回来
	printf("%d\n", n);
	return 0;
}

用 -120 验证奇偶位交换是否成功

运行结果:

  时间过得很快,眨眼间C语言的博客已经接近完结了,后续有时间还会发布C语言的一些经典面试题解,大家尽请期待。 

  下一篇博客就开始学习并更新数据结构与算法的内容,感谢各位对纪宁的支持。

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

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

相关文章

F.interpolate 数组采样操作

功能&#xff1a;利用插值方法&#xff0c;对输入的张量数组进行上\下采样操作&#xff0c;换句话说就是科学合理地改变数组的尺寸大小&#xff0c;尽量保持数据完整。 在计算机视觉中&#xff0c;interpolate函数常用于图像的放大(即上采样操作)。比如在细粒度识别领域中&…

【C#】MVC页面常见的重定向方式和场景

本篇文章主要简单讲讲&#xff0c;C# MVC 页面常见跳转或者重定向的方式和场景。 在实际项目开发中&#xff0c;在一些特定场景肯定会用到重定向&#xff0c;比如&#xff1a;不同角色跳转到不同视图地址 目录 一、种常见重定向方式1.1、RedirectToAction1.2、RedirectToRoute1…

猿人学14题—备而后动-勿使有变

猿人学14题—备而后动-勿使有变 抓包分析大致流程 mz参数生成m的值定位&参数组成补环境首先简单处理下十六进制编码问题提示&#xff1a;ReferenceError: window is not defined提示&#xff1a;document is not defined提示&#xff1a;$ is not definedASN1 is not defin…

【探索人工智能】我与讯飞星火认知大模型的对话

文章目录 讯飞星火认知大模型的地址概要讯飞星火认知大模型的发展历程讯飞星火认知大模型的主页利用讯飞星火大模型解决一些基本的数学问题讯飞星火认知大模型与OpenAI,ChatGPT没有关系&#xff01;让讯飞星火认知大模型编写传奇代码hello world小结 讯飞星火认知大模型的地址 …

chatgpt使用及辅助编程方面的体验

chatgpt使用及辅助编程方面的体验 文章目录 chatgpt使用及辅助编程方面的体验1 引言2 辅助编程体验2.1 辅助编写代码2.2 找出代码问题2.3 代码优化2.4 解释代码结束语 1 引言 最近几个月什么最火&#xff0c;那一定时chatgpt,虽然在国内使用存在各种限制&#xff0c;但是还是挡…

el-select和el-checkBox实现下拉菜单全选功能

el-select 和 el-checkbox 实现下拉菜单全选功能 示例代码&#xff1a; <el-selectpopper-class"select-container"v-model"ids"placeholder"请选择目标":multiple-limit"20"multiplefilterablecollapse-tagsclass"wd400&qu…

20230721 Essex UK, Dongbing Gu 公开讲座--机器人前沿

个人主页&#xff1a; https://www.essex.ac.uk/people/GUDON81301/dongbing-gu 机器人领域任务的特点&#xff1a;dull, dirty, dangerous tasks in remote spaces 机器鱼&#xff1a; 实时港口环境监测 机器鱼群探索算法 化学传感器 水面声呐定位系统/SLAM/通信问题 Robotic …

RocketMQ教程-安装和配置

Linux系统安装配置 64位操作系统&#xff0c;推荐 Linux/Unix/macOS 64位 JDK 1.8 Maven3.0 yum 安装jdk8 yum 安装maven 1.下载安装Apache RocketMQ RocketMQ 的安装包分为两种&#xff0c;二进制包和源码包。 点击这里 下载 Apache RocketMQ 5.1.3的源码包。你也可以从这…

网络安全 Day18-计算机网络知识03

计算机网络知识03 1. 路由器排查故障2. 设置和修改网关3. 设置修改DNS4. 私网地址5. VMware虚拟机NAT模式下上网原理6. DHCP工作原理 1. 路由器排查故障 排查网线&#xff0c;排查网卡&#xff0c;排查网卡的驱动查看网卡IP&#xff0c;没有配置IP、网关、DNS配置正确ping百度…

R语言贝叶斯METROPOLIS-HASTINGS GIBBS 吉布斯采样器估计变点指数分布分析泊松过程车站等待时间...

原文链接&#xff1a;http://tecdat.cn/?p26578 指数分布是泊松过程中事件之间时间的概率分布&#xff0c;因此它用于预测到下一个事件的等待时间&#xff0c;例如&#xff0c;您需要在公共汽车站等待的时间&#xff0c;直到下一班车到了&#xff08;点击文末“阅读原文”获取…

行为型模式 - 状态模式

概述 【例】通过按钮来控制一个电梯的状态&#xff0c;一个电梯有开门状态&#xff0c;关门状态&#xff0c;停止状态&#xff0c;运行状态。每一种状态改变&#xff0c;都有可能要根据其他状态来更新处理。例如&#xff0c;如果电梯门现在处于运行时状态&#xff0c;就不能进…

MySQL—事务

MySQL—事务 &#x1f50e;定义&#x1f50e;事务的特性原子性一致性持久性隔离性 &#x1f50e;并发执行事务可能产生的问题脏读不可重复读幻读总结 &#x1f50e;MySQL—事务的隔离级别 &#x1f50e;定义 事务的本质是将多条 SQL 语句打包成一个整体 要么全部成功, 要么全部…

html a标签换行显示

文章目录 用css display属性不用css&#xff0c;可以用<br>标签换行示例 用css display属性 可以使用CSS的display属性来实现多个a标签每行显示一个。 HTML代码&#xff1a; <div class"link-container"><a href"#">Link 1</a>…

# **基于TiDB Binlog架构的主备集群切换操作手册**

作者&#xff1a; Liuhaoao 原文来源&#xff1a; https://tidb.net/blog/dc65ef62 操作背景&#xff1a;最近手头有个系统&#xff0c;刚做完灾备建设及数据同步&#xff08; 文章链接在这 &#xff09;&#xff0c;需要进行灾备切换演练&#xff0c;验证灾备库建设是否…

HCIA静态路由综合实验(eNSP)

实验题目及要求&#xff1a; 1、分析IP地址分配。 主干IP掩码均为30&#xff1b; 环回IP掩码为28&#xff0c;方便汇总掩码27&#xff1b; 然后预留部分IP地址。 如下图&#xff1a; 2、按如上图片要求连接设备&#xff0c;并标记好IP分配信息&#xff0c;便于命令配置时一…

网页生成PDF表格诡异多出空白

环境&#xff1a; axios: 0.27.0 egg: 2.35.0 pdf-lib: 1.17.1 puppeteer-core: 17.1.3 node: 16.20.0 element-plus: 2.3.2 vue: 3.2.47 背景&#xff1a; 一个报告页面含有多个统计表格和描述文字&#xff0c;生成PDF用于下载查看&#xff0c;页面使用vue3element-plus…

使用ffmpeg合并视频遇到的坑

下面以Linux环境介绍为主 1.ffmpeg可执行命令不同的环境是不同的&#xff0c;Linux在执行命令前还需要授权。 2.合并视频命令&#xff1a; 主要命令: {} -f concat -auto_convert 0 -safe 0 -i {} -y -c:v copy 坑一&#xff1a;其中第一个花括号替换的是可执行命令所在的…

如何计算 FPS(帧率)

FPS(Frames Per Second)&#xff0c;每秒多少帧 在 val.py 中&#xff0c;需要修改参数 batch-size1 同时别忘了修改自己数据集的 yaml 文件以及训练好的 pt 文件&#xff0c;如下图&#xff1a; 修改完成后&#xff0c;运行 val.py&#xff0c;得到结果&#xff0c;如下图&a…

Debian部署Tomcat 注册服务并设置开机启动

目录 写在前面 1.准备工作 2.在Linux下安装Tomcat 3.Tomcat注册服务并设置开机启动 3.1.使用 rc.local 配置开机启动 3.2.使用/etc/init.d 3.3.systemd配置&#xff08;通用方式&#xff09; 在Debian系统上安装Tomcat并将其注册为服务以及设置开机启动的步骤。Tomcat是一…

Android Studio 提示 Failed to initialize editor问题的解决

Android Studio 从2018的版本升级到2021年的版本后&#xff0c;无法预览xml。我查了很久&#xff0c;最后发现是Gradle的版本和工具不匹配&#xff0c;按照开发工具的提示&#xff0c;升级版本即可&#xff0c;我的是从3.2.1升级到了4.2.2