ONEPIECE!程序环境和预处理——C语言最终章

时间过得飞快呀,从第一篇blog到现在,已经有三四个月的时间了,而我们终于也迎来了C语言的最终章——程序环境和预处理!加油吧朋友们,ONEPIECE就在眼前~

目录

 

一、程序的"翻译环境"和"运行环境"

 二、详解"编译环境"

三、"运行环境"

四、"预处理"详解

4.1预定义符号

4.2 小能手#define 

4.2.1 #define 定义标识符

4.2.2 #define 定义宏

4.2.3 #define 替换规则

4.2.4 巧用 # 和 ##

4.2.4.1 # 的使用

4.2.4.2 ##的使用

 4.2.5 带副作用的宏参数

4.2.6 宏和函数的对比

4.3 #undef

 4.4 条件编译

4.5文件包含

4.5.2 嵌套文件包含


 一、程序的"翻译环境"和"运行环境"

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

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是运行环境,它用于实际执行代码。

test.c是文本信息的代码——源代码 || test.exe是可执行程序——二进制的指令

 二、详解"编译环境"

编译环境其实又分为编译和链接两部分:编译由编译器执行、链接由链接器执行。

编译器和链接器我们都可以在我们的电脑中搜索到:

  

下面我们介绍一下程序的编译过程: 

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

其实编译还可以继续细分:预编译、编译、汇编。

下面我们具体看一下每一步系统都干了些什么: 

我们可以看到几个阶段都强调了“符号”、“符号表”的操作,其实这些操作都是在为“链接”这个阶段做铺垫。

我们举个例子:假设我们的文件有两个 test.c 和 add.c


 符号汇总:

在 add.c 中,系统汇总了一个符号(系统一般只汇总应用全局的符号) add

在 test.c 中,系统汇总了符号:addmain、printf(这里忽略不谈) 

生成符号表:

注:这里汇总的每一个符号都是有其地址的,在这里我们假设一下。

合并段表:

而且,目标文件都有其特定的格式,在合并符号表时,相同段的数据会合并到一起:

合并符号表: 

合并两个符号表时,将 add.c 和 test.c 中的符号表合并到一起:

 最终系统都是通过最后这个符号表记录的地址来找到对应的符号位置。

三、"运行环境"

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

四、"预处理"详解

经过上面的学习,我们可能已经忘了,"预处理"是哪个阶段来着?

它是"编译"的第一个小阶段。 

4.1预定义符号

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

用例: 

int main()
{printf("%s\n", __FILE__);//进行编译的文件printf("%d\n", __LINE__);//文件当前的行号printf("%s\n", __DATE__);//文件被编译的日期printf("%s\n", __TIME__);//文件被编译的时间//printf("%d\n", __STDC__);//当前使用是的VS2022不遵循ANSI Creturn 0;
}

4.2 小能手#define 

4.2.1 #define 定义标识符

语法:
#define name stuff

注意:define定义标识符时,最后不要加 ; 因为系统是完全把 stuff 替换到 name的位置。

相信小伙伴们对定义标识符已经很熟悉啦,下面我们来新介绍一个语法:

4.2.2 #define 定义宏

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

下面是宏的申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中。

注意:
参数列表的左括号必须与name紧邻

如果两者之间有任何空白存在,参数列表就会被解释为 stuff 的一部分

举个栗子:

#define ADD(x, y) (x) + (y)
int main()
{printf("%d\n", ADD(3, 22));return 0;
}

是不是很神奇,用宏也可以做函数的工作,但是宏在传参时也有可能会发生小错误:

 其实宏能做的不只是这些,宏能替换大部分的函数。

4.2.3 #define 替换规则

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。//检查宏中是否存在其他#define并替换
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

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

注意:

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

4.2.4 巧用 # 和 ##

4.2.4.1 # 的使用

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

#define PRINT(x,format) printf(#x "的值是:"format"\n", x)
int main()
{float Love_date = 3.22;PRINT(Love_date, "%.2f");return 0;
}

4.2.4.2 ##的使用

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

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

 4.2.5 带副作用的宏参数

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

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

4.2.6 宏和函数的对比

宏的优点:

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

宏的缺点:

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。

    除非宏比较短,否则可能大幅度增加程序的长度(每使用一次都要替换一次)。
2. 宏是没法调试的。宏直接替换,我们无法逐步分析。

3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错

另外,为了区分宏和函数,在宏和函数的命名时,宏一般全大写,如MAX、DOUBLE...;而函数名不全大写。

4.3 #undef

#undef 用于移除一个宏定义

 4.4 条件编译

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

常见的条件编译指令:

1.(同if else)

#if  常量表达式

//操作

#endif
//常量表达式由预处理器求值。

如:

#if 1printf("u r a genius!\n");
#endif
//...

2.多个分支的条件编译(同if elseif else)
#if 常量表达式
//...
#elif 常量表达式    //可以无限多
//...
#else
//...
#endif        //结束标志,必须有

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

//以上两种写法相同
#if !defined(symbol)
#ifndef symbol

//以上两种写法相同

4.嵌套指令
#if defined(OS_UNIX)
    #ifdef 条件1
        //...
   
#endif
   
#ifdef 条件2
        //...
 
  #endif

#elif defined(OS_MSDOS)
    #ifdef 条件3
        //...
 
  #endif
#endif

4.5文件包含

4.5.1 头文件被包含的方式

头文件的包含有2中形式:

1.包含本地文件(自己的.h文件)

#include"xxx.h"

2.包含标准库的头文件

#include<xxx.h>

本地文件查找:

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

标准库文件查找:

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

4.5.2 嵌套文件包含

当出现以上这种场景时,就造成了文件内容的重复。 

这时我们就可以使用条件编译。或者在VS中我们创建.h文件中,它也会生成#pragma once ,这也可以避免头文件的重复引入。

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

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

相关文章

Biotech - 环状 mRNA 的 LNP 递送系统 与 成环框架

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/133992971 环状 RNA&#xff08;或 circRNA &#xff09;是一种单链 RNA&#xff0c;与线性 RNA 不同&#xff0c;形成一个共价闭合的连续环。在环…

Hadoop+Hive+Spark+Hbase开发环境练习

1.练习一 1.数据准备 在hdfs上创建文件夹&#xff0c;上传csv文件 [rootkb129 ~]# hdfs dfs -mkdir -p /app/data/exam 查看csv文件行数 [rootkb129 ~]# hdfs dfs -cat /app/data/exam/meituan_waimai_meishi.csv | wc -l 2.分别使用 RDD和 Spark SQL 完成以下分析&#xf…

python爬虫入门(三)正则表达式

开源中国提供的正则表达式测试工具 http://tool.oschina.net/regex/&#xff0c;输入待匹配的文本&#xff0c;然后选择常用的正则表达式&#xff0c;就可以得出相应的匹配结果了 常用的匹配规则如下 模  式描  述\w匹配字母、数字及下划线\W匹配不是字母、数字及下划线的…

零代码编程:用ChatGPT多线程批量将PDF文档转换为word格式

pdf2docx是Python的一个库&#xff0c;可以很方便的将PDF文档转换为word格式&#xff0c;首先安装这个库。 然后在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个文档格式转换的任务&#xff0c;具体步骤如下&#xff1a; 打开F盘的Books文件…

强化学习代码实战(1)

机器人领域&#xff1a;控制&#xff0c;规划&#xff0c;感知等都可以用&#xff0c;可以把它作为一个优化过程&#xff0c;那么任何需要优化的问题都可以用它解决。 1.应用 深度学习&#xff1a;智能感知&#xff0c;解决智能如何理解这个世界的问题。 强化学习&#xff1a…

python之代理ip的配置与调试方法详解

代理IP在Python中是一种强大的工具&#xff0c;它可以用于隐藏真实IP地址、绕过访问限制、提高数据爬取和网络请求的效率等。下面将详细介绍Python中代理IP的配置与调试方法&#xff0c;帮助您更好地理解和应用代理IP。 1. 选择合适的代理IP 在使用代理IP之前&#xff0c;需要…

vue v-for

目录 前言&#xff1a;Vue.js 中的 v-for 指令 详解&#xff1a;v-for 指令的基本概念 用法&#xff1a;v-for 指令的实际应用 1. 列表渲染 2. 动态组件 3. 表单选项 4. 嵌套循环 5. 键值对遍历 解析&#xff1a;v-for 指令的优势和局限性 优势&#xff1a; 局限性&a…

【数据结构】B树与B+树的联系与区别

1. 概念 一棵m阶B树&#xff1a; 树中每个结点至多有m棵子树。(即至多含有m-1个关键字&#xff0c;两颗子树指针夹着一个关键字)&#xff1b;若根结点不是终端结点&#xff0c;则至少有两颗子树。(至少一个关键字)&#xff1b;除根结点外的所有非叶子结点至少有[m/2]棵子树。…

【前端设计模式】之享元模式

享元模式是一种结构型设计模式&#xff0c;它通过共享对象来减少内存使用和提高性能。在前端开发中&#xff0c;享元模式可以用于优化大量相似对象的创建和管理&#xff0c;从而提高页面的加载速度和用户体验。 享元模式特性 共享对象&#xff1a;享元模式通过共享相似对象来…

网络通信和tcp协议

一、计算机网络架构模型 1、OSI七层模型 2、TCP/IP模型 3、TCP/IP协议族 无论是什么网络模型&#xff0c;都是为上一层提供服务&#xff0c;抽象层建立在低一层提供的服务上&#xff0c;每层都对应不同的协议 4、地址和端口号 1&#xff09;MAC地址 MAC 地址共 48 位&#…

RAM(recognize anything)—— 论文详解

一、概述 1、是什么 RAM 论文全称 Recognize Anything: A Strong Image Tagging Model。区别于图像领域常见的分类、检测、分割&#xff0c;他是标记任务——即多标签分类任务&#xff08;一张图片命中一个类别&#xff09;&#xff0c;区分于分类&#xff08;一张图片命中一个…

读书笔记:Effective C++ 2.0 版,条款43(多继承)、条款44(概念明确)、条款45-50(杂项)

条款43: 明智地使用多继承 并没有禁止&#xff0c;从概念上讲&#xff0c;多继承可能更符合真实世界。 条款44: 说你想说的&#xff1b;理解你所说的 概念明确 条款45: 弄清C在幕后为你所写、所调用的函数 隐性成本&#xff0c;看下编译后的c、asm源码。 条款46: 宁可编译和…

Java基础(第一期):IDEA的下载和安装(步骤图) 项目结构的介绍 项目、模块、类的创建。第一个代码的实现

文章目录 IDEA1.1 IDEA概述1.2 IDEA的下载和安装1.2.1 下载1.2.2 安装 1.3 IDEA中层级结构介绍1.3.1 结构分类1.3.2 结构介绍project&#xff08;项目、工程&#xff09;module&#xff08;模块&#xff09;package&#xff08;包&#xff09;class&#xff08;类&#xff09; …

安全渗透测试之信息收集

一、什么需要收集? 1.1 whois 得到域名注册人的信息(邮箱、电话、姓名) 1.2子域名 可以扩大攻击范围,子域名一定是有关联的,很多时候基本上都同属一个公司 1.3端口探测 危险端口能直接爆破入侵,一个IP能搭建多个网站,分布在不同端口 1.4目录扫描 目录扫描有时能…

【Javascript】等于与全等于

var a1;if(a1){console.log(你好&#xff01;&#xff01;);} 如果a赋值为 1 的时候 var a1;if(a1){console.log(你好&#xff01;&#xff01;);}仍然会执行 console.log(你好&#xff01;&#xff01;); 所以在开发中如果if语句里要使用等于的时候使用 var a1;if(a1)…

基于Java的疫苗接种管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

图像分割 人脸分割CVPR2023笔记

目录 cvpr2023的分割 轻量级语义分割 DFNet ICNet BiSeNets 人脸分割: M2SNet创新点 损失函数: LossN

AM@麦克劳林公式逼近以及误差分析

abstract 麦克劳林公式及其近似表示的应用误差估计和分析 Lagrange型泰勒公式的估计误差 由Lagrange型余项泰勒公式可知,多项式 p n ( x ) p_n(x) pn​(x)近似表达函数 f ( x ) f(x) f(x)时,其误差为 ∣ R n ( x ) ∣ |R_{n}(x)| ∣Rn​(x)∣ R n ( x ) R_{n}(x) Rn​(x) f …

windows服务器和linux服务器的ssh免密登录失败的解决方案

1. windows服务器的ssh免密登录失效的解决方法 原因&#xff1a; 1. 去掉了C:\ProgramData\ssh\sshd_config文件中的两行配置&#xff0c;也就是注释掉&#xff1a; #Match Group administrators #AuthorizedKeysFile PROGRAMDATA/ssh/administrators_authorized_keys 2. aut…

LeetCode 11. 盛最多水的容器

盛水最多的容器 题目链接 11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。…