C程序环境及预处理

​​​​​文章目录

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

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.mzph.cn/news/5864.shtml

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

相关文章

Vue3状态管理库Pinia——核心概念(Store、State、Getter、Action)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

Java将数据集合转换为PDF

这里写自定义目录标题 将数据集合转换为pdf引入包工具类测试代码导出效果 将数据集合转换为pdf 依赖itext7包将数据集合转换导出为PDF文件 引入包 <properties><itext.version>7.1.11</itext.version> </properties><dependency><groupId&…

什么是HTTP 500错误,怎么解决

目录 什么是HTTP 500 HTTP 500错误的常见原因&#xff1a; 如何修复HTTP 500 总结 什么是HTTP 500 错误 HTTP 500内部服务器错误是指在客户端发出请求后&#xff0c;服务器在处理请求过程中发生了未知的问题&#xff0c;导致服务器无法完成请求。HTTP 500错误是一个通用的服…

Spring-缓存初步认识

Spring-缓存 简单介绍 缓存是一种介于数据永久存储介质和数据应用之间的数据临时存储介质缓存有效提高读取速度&#xff0c;加速查询效率 spring使用缓存方式 添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring…

海康摄像头开发笔记(一):连接防爆摄像头、配置摄像头网段、设置rtsp码流、播放rtsp流、获取rtsp流、调优rtsp流播放延迟以及录像存储

文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131679108 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

每日一题——反转链表

题目 给定一个单链表的头结点pHead(该头节点是有值的&#xff0c;比如在下图&#xff0c;它的val是1)&#xff0c;长度为n&#xff0c;反转该链表后&#xff0c;返回新链表的表头。 数据范围&#xff1a;0≤n≤1000 要求&#xff1a;空间复杂度 O(1) &#xff0c;时间复杂度 O…

Python实战项目——旅游数据分析(四)

由于有之前的项目&#xff0c;所以今天我们直接开始&#xff0c;不做需求分析&#xff0c;还不会需求分析的可以看我之前的文章。Python实战项目——用户消费行为数据分析&#xff08;三&#xff09; 导入库 import numpy as np import pandas as pd import matplotlib.pyplo…

PHP后台登录功能单账号登录限制

PHP后台登录功能单账号登录限制 单账号登陆是什么第一步创建数据表第二步创建登录页面test2.html第三步创建登录提交test2.php第四步访问后台首页第五步演示 单账号登陆是什么 一个用户只能登录一个账号通常被称为单账号登录限制或单用户单账号限制。这意味着每个用户只能使用…

Linux 学习记录53(ARM篇)

Linux 学习记录53(ARM篇) 本文目录 Linux 学习记录53(ARM篇)一、内存读写指令1. 在C语言中读取内存2. 指令码及功能3. 格式4. 使用示例5. 寻址方式(1. 前索引方式(2. 后索引方式(3. 自动索引 6.批量寄存器操作指令(1. 操作码(2. 格式(3. 使用示例(4. 地址增长方式>1 ia后缀&…

========Java基础——小结1========

一、Java 两大版本 Java 主要分为两个版本: Java SE 和Java EE。 Java SE 全称Java Platform Standard Edition&#xff0c;是 Java 的标准版&#xff0c;主要用于桌面应用程序开发&#xff0c;它包含了 Java 语言基础、JDBC (Java 数据库连接)、I/O (输入/输出)、TCP/IP 网络…

股票基金入门知识

1.开盘价和收盘价如何产生 时间9:30-11:30 13:00-15:00 集合竞价时间段&#xff1a;9:15-9:25 以此产生开盘价 最后集中竞价时间段&#xff1a;深市14:57-15:00 &#xff0c;以此产生收盘价。 沪市则采用最后一分钟加权得出收盘价影响股价的因素 市场投资情绪&#xff0c;宏观…

Hadoop——DataGrip连接MySQL|Hive

1、下载 DataGrip下载&#xff1a;DataGrip: The Cross-Platform IDE for Databases & SQL by JetBrains 2、破解 破解链接&#xff1a;https://www.cnblogs.com/xiaohuhu/p/17218430.html 3、启动环境 启动Hadoop&#xff1a;到Hadoop的sbin目录下右键管理员身份运行…

【C++】list 模拟笔记

文章目录 list定义结点类&#xff08;list_node&#xff09;为什么封装迭代器为类 &#xff1f;库里面模板多参数的由来 &#xff1f;为什么普通迭代器不能隐式类型转换成const迭代器&#xff1f;迭代器位置指向及其返回值和整体代码 list list 和前面学习的 string 和 vector …

微信小程序使用ECharts的示例详解

目录 安装 ECharts 组件使用 ECharts 组件图表延迟加载 echarts-for-weixin 是 ECharts 官方维护的一个开源项目&#xff0c;提供了一个微信小程序组件&#xff08;Component&#xff09;&#xff0c;我们可以通过这个组件在微信小程序中使用 ECharts 绘制图表。 echarts-fo…

excel中单行换成多行

今天碰以下情况&#xff1a; 这在excel表中是在一个单元格&#xff0c;现在需要对其进行转换&#xff0c;将一个单元格换成多行 步骤&#xff1a; 1.删除换行符&#xff0c;添加一个逗号 2.选择数据-分列-分隔字符-逗号-确定 3.复制上述数据&#xff0c;选择性粘贴-转置 完…

2816. 判断子序列

题目链接&#xff1a; 自己的做法&#xff1a; #include <bits/stdc.h>using namespace std;const int N 1e5 10; int a[N], b[N]; int main() {int n, m;bool flag true;scanf("%d%d", &n, &m);for (int i 0; i < n; i) scanf("%d"…

哈希:探索快速的数据存储和搜索方法

哈希&#xff1a;探索快速的数据存储和搜索方法 哈希表作为一种高效的数据存储结构&#xff0c;可以使数据的存储位置与关键码之间建立一一映射的关系&#xff0c;从而加快元素的搜索速度。然而&#xff0c;哈希方法也面临着哈希冲突的问题&#xff0c;即不同的关键字通过相同…

dxf怎么转换成PDF格式?转换方法其实很简单

PDF文件是一种可靠的文件格式&#xff0c;可以在各种操作系统和软件上打开和查看。而dxf是CAD文件的一种格式&#xff0c;打开它一般都是需要相关的操作软件才能打开&#xff0c;不是特别方便&#xff0c;将dxf文件转换成PDF格式就可以很好的解决这一问题&#xff0c;下面教大家…

Kafka - Primie Number of Partitions Issue Consumer Group Rebalance

文章目录 生产者&#xff1a;将数据写入 Kafka 的客户端。 消费者&#xff1a;从 Kafka 中读取数据的客户端。 Topic&#xff1a;Kafka 中用于组织和存储数据的逻辑概念&#xff0c;类似于数据库表。 Record&#xff1a;发送到 Topic 的消息称为 Record。 Partition&#x…

List有值二次转换给其他对象报null

List<PlatformUsersData> listData platformUsersMapper.selectPlatformUserDataById(data); users.setPlatformUsersData(listData);为什么listData 有值&#xff0c;users.getPlatformUsersData&#xff08;&#xff09;仍然为空在这段代码中&#xff0c;我们假设listD…