系统程序的编译与处理

news2024/12/23 9:00:00

目录: 

一,程序的编译与执行

二,预处理详解

三,#define的运用

四,条件编译


一,程序的编译与执行

1,编译环境

        首先,要说明的是,计算机只能识别二进制指令,在编写程序时,无论是何种语言,系统根本就识别不了,要想让机器来识别我们用的语言,就必须要通过编译器来进行编译,形成二进制指令的目标文件(.obj)。

        在源程序中,通常有很多个源文件,在一个源程序的所有源文件中,当程序运行时,组成一个程序的每个源文件都要通过编译过程分别转换成可识别的目标文件,然后系统才会将这些目标文件进行下一步处理。所以,这也就是为什么在一个源程序中不能运行两个不同的主程序,这样可能会出现系统崩溃。

        接下来我们来详细研究一下编译环境。编译环境也细分为编译链接,编译又可细分为预编译,编译,汇编。

        预编译:预编译也叫预处理。在预编译中,系统会将代码的注释删除,宏的符号进行替换,以头文件的包含所运用。在文本操作中,所有的预处理指令都是在预处理阶段处理的。

        编译:在预处理完毕后就进入了编译环节,在编译环节中,系统会把C代码翻译成汇编指令,即形成汇编代码,在此环节中,会一系列进行语法分析,词法分析,语义分析,符号汇总等。

        汇编:编译过后就进入了汇编环节,在汇编中,系统根据汇编指令将会形成二进制的目标文件,在这些目标文件中都是一些人为看不懂的二进制指令。除此之外,还会形成以一个符号表,在符号表中存放了一些汇总的符号和相关的地址。

        链接:在链接中,系统会合并段表,并进行符号表的合并和重定位。

具体形式如下图所示:

 2,执行环境

        在编译结束后,系统已经形成了一系列的二进制的目标文件。在执行环境中,系统执行的就是这些目标文件0,每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序,在此过程中,链接器同时也会引入标准C(ANSI C)函数库中任何被该程序所用的函数,而且它可以搜索程序员个人程序库,将其需要的函数也链接到程序中。

程序的执行与编译导图


二,预处理详解

        预处理是系统首要处理代码的环节,系统内置的符号是预处理典型处理的符号。

        首先,我们先了解下预定义符号。在C程序中,预定义符号都是语言内置的,而这些预定义符号,在预编译时就进行预处理,其中,预定义符号有以下几种:

        __FILE__:用来显示进行编译源文件的来源

        __LINE__:显示文件当前的行数

        __DATE__:显示文件被编译的日期

        __TIME__:显示文件被编译的时间

        __STDC__:此符号遵循ANSI C(标准C),如若当前编译器也遵循ANSI C(标准C),其值为1,否则此符号未定义,将会报错。

具体例子代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
int main()
{
    fprintf(stdout, "%s\n", __FILE__);//__FILE__代表被编译环境的名字
    fprintf(stdout, "%d\n", __LINE__);//__LINE__代表此时的行数
    fprintf(stdout, "%s\n", __TIME__);//此时运行的时间
    fprintf(stdout, "%s\n", __DATE__);//此时的日期
    //fprintf(stdout, "%s\n", __STDC__);输出错误
    //因为在VS中是不支持ANSI C,即标准C,任何编译器都要在此基础上扩充。若支持,则值为1,否则未定义

    return 0;
}

运行图:


三,#define的运用

1,#define的标识符

       在#define的运用中,最简单的用途就是用#define来定义标识符,形式为#define x y。经#define定义后,如同换了个名字,即把x的名字改为了y。两者之间的作用效果还是一样的。代码如下:

#define name 5

#define type int

2,#define的宏定义

       #define除了用来标识,还可以定义用来定义宏。在#define机制中,允许把参数替换到文本中,这种类似于函数的实现被称为宏或定义宏,而预处理过程中,宏的定义将会被程序替换,但在替换过程也存在优先级,形式代码和注意要素如下:

#include<stdio.h>
//定义一个数的平方宏
#define num(x) x*x
#define arr(x) (x)*(x)
int main()
{
    int a = 3;
    int r = num(a + 2);
    int t = arr(a + 2);
    //经过预编译后r=a+2*a+2=3+2*3+2=11
    fprintf(stdout, "%d\n", r);//输出11
    //因为加上了括号有了优先级,即经过预编译后t=(a+2)*(a+2)=5*5=25
    fprintf(stdout, "%d\n", t);//输出25
    return 0;
}

宏的代码替换如图:

3,’#‘和’##‘的宏作用

         在宏的使用过程中,还可以使用‘#’‘##’的运用,‘#’用来进行字符或字符串的参数替换,‘##’用来进行参数合并,而在宏的输出中,用format可以进行任意类型的输出,具体形式代码如下:

#include<stdio.h>
//在其中用"#n"将会把字符串参数进行替换
#define num(n) fprintf(stdout,"Hello "#n" World %d\n", n)
//在宏定义参数后面加上format后可输出任意类型,具体格式如下
#define arr(n,format) fprintf(stdout,"Hello "#n" World "format"\n", n)//"format"会将其值代入
int main()
{
    int abc = 10, bcd = 20;
    float pp = 1.56f;
    int pa = 15;
    num(abc);//用"#参数"将会替换字符串,输出Hello abc World 10
    num(bcd);//用"#参数"将会替换字符串,输出Hello bcd World 20
    //用format形式将会输入任何类型的参数

    arr(pp, "%f");
    arr(pa, "%d");
    return 0;
}

        在运行‘##’代码之前,我们要先明白,宏的替换中若是替换成了类型变量,则在输出的时候会以变量类型的形式输出,请看以下例子:

#include<stdio.h>
#define m a
int main()
{
    int a = 5;
    fprintf(stdout, "%d", m);//输出5,因为m变成了a,而a是变量,值为5
    return 0;
}

        ‘##’的合并就是根据以上为基础的,’##‘是将宏的参数进行合并在一起,代码形式如下,

#include<stdio.h>
//##可以将两个数进行合并
#define CTA(a,b) a##b//相当于#define CTA(a,b) ab
int main()
{
    int ab = 2023;
    //在##的作用宏中,a##b相当于ab,##是合成符,将分离的片段合成一个片段
    fprintf(stdout, "%d\n", CTA(a, b));//输出2023
    fprintf(stdout, "%d\n", ab);
    return 0;
}
  

4,宏参数的副作用

        宏在使用过程中也要进行传参,然而,在这里需提醒的是如若在传参的过程中参数会发生变化,就会形成副作用的变化,即宏数值逻辑的变化,代码例子如下。

#include<stdio.h>
#define max(x,y) ((x)>(y)?(x):(y))
int main()
{
    int a = 5, b = 6, c = max(a++,b++);
    //在这里,c先被预处理替换成c=((a++)>(b++)?(a++):(b++))
    //前缀++,在表达式中先给值再++,所有经过c后a=6,b=8(b经过了两次++)

    fprintf(stdout, "%d %d %d\n", a, b, c);//输出6 8 7
    return 0;
}

        以上代码在宏传参的时候参数就在不断发生数值变化,形成了宏的副作用。

5,#undef的移除宏的操作

        在宏定义后,如若不想在使用某个宏,我们可以使用#undef来进行移除操作,用#undef移除后的宏就等于直接被删除了,如若在使用被此宏将会报错未定义,代码如下:

#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
    int a = 10, b = 20;
    int c = MAX(a, b);
    fprintf(stdout, "%d", c);
#undef MAX//将宏全部移除,没有此宏了
    int d = MAX(a, c);//会报错,因为宏已经被移除了
    fprintf(stdout, "%d", d);
    return 0;
}

6,宏于函数的对比

        经过宏的几个运用我们大概明白,宏与函数有着相似的作用,但两者之间却各有千秋。当执行简单的运算时,我们通常使用宏来完成,因为用于调用函数和从函数返回的代码可能比实际执行这个小类型计算工作所需要的时间更多,在小的运算中宏比函数在程序的规模和速度方面更胜一筹。除此之外,函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用,反之,宏可以适用于任何类型。

        然而,宏也有自身的缺点,宏的缺点如下:

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

2,宏是没办法调试,每次出错的时候必须人为观察。

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


四,条件编译

        在编译一个程序的时候我们如果将一条语句(即一组语句)编译或者放弃是很方便的。因为我们有条件编译指令,这个指令的编译相当于就是条件编译,而条件编译在预编译时会被处理。

        有关条件编译的指令#if,#elif,#else,#endif,#if defined等。

1,条件语句的判断

        用来条件语句的判断有四种:#if,#elif,#else,#endif

        #if语句于条件判断语句if相似,如若#if判断的语句值为0,则后面的代码将会在预编译过程被系统删除,如若值为1则执行后面的代码。

        #elif于条件语句中else if条件语句详细,它是根据#if后面的又一次判断

        #else与条件语句else作用也是相同的,在条件编译不满足时执行。

        #endif是条件编译指令的结束标志,#endif是与#if#elif共同使用的,倘若出现#if#elif就必须有#endif,否则系统会报错。

具体代码如下:

#include<stdio.h>
#define M 0
int main()
{
//当条件成立,即值为1时,执行下面的语句,若不成立,在预处理时代码就被删除了
#if M == 1
//跟if条件语句不同的是,if语句每次执行时都有此代码,#if中是直接把代码删除
    fprintf(stdout, "%d", M);
#elif M == 2//多种条件编译的判断
    fprintf(stdout, "Hello World");
#else//跟条件语句中的else作用同理
    fprintf(stdout, "兔子\n");
#endif//终止#if的判断指令,在#endif后的语句仍可执行
    fprintf(stdout, "%d", 5);
    return 0;
}

2,条件宏的判断

        通常使用#if defined的形式来判断宏。#if defined的作用是条件判断是否定义宏,与#if不同的是当用#if defined判断的宏存在时才执行后面的代码,当不存在时系统在预编译时将会自动删除后面的代码。具体例子如下:

#include<stdio.h>
#define MAX 5
int main()
{    
#if defined(MAX)//如若存在MAX这个宏,则下面的代码执行,否则在预处理时删除以下代码
//其中#if defined(MAX)也可写成#ifdef MAX,两者之间效果一样
    fprintf(stdout, "%d", MAX);
#endif
    return 0;
}    

3,条件编译的嵌套使用

        条件编译与条件语句存在类似功能,条件编译跟条件语句一样,也存在嵌套的使用。嵌套的使用在平常的代码中其实也常见,在系统运维或跨平台使用时,经常多次使用这些条件编译的嵌套使用来实现预想的目的。有关嵌套的使用代码如下:

#include<stdio.h>
int main()
{
//在多次嵌套的使用在跨平台时的使用较多,在跨平台时会大量的使用宏的嵌套
#if defined(WIN)
    #ifdef LIUNX
        Wliunx();
    #endif
    #ifdef UNIX
        opper();
    #endif
#elif defined(OPP)
    #ifdef NUIO
        liunx();
    #endif
#endif
    return 0;
}//在用#if和#endif条件预处理时相当于注释,在预编译过程中判断是否直接将其删除

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

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

相关文章

文件共享服务器(五)sicis

目录 前言 一、概述 1.iscsi概念 2.iscsi介绍 3.相关名词 二、实验 1.构建iscsi服务 2.实现步骤 服务器端 客户端 3.注意事项 总结 前言 iSCSI是由IBM发明的基于以太网的存储协议&#xff0c;该协议与SUN的NFS协议都是为了解决存储资源共享问题的解决方案。两者意图…

传统商超苦战即时零售,或沦为炮灰

眼下&#xff0c;在美团闪购、京东到家、饿了么、淘宝买菜/淘鲜达、盒马&#xff0c;还有朴朴超市、叮咚买菜等一众类超市App或者平台的绝情裹挟下&#xff0c;包含沃尔玛、家乐福、永辉、大润发、联华、华润万家、步步高、中百等等这些传统商超企业巨头&#xff0c;正过得越来…

Unity噪声图生成(编辑器扩展)

最近发现项目里很多shader都需要噪声图&#xff0c;&#xff08;shadergraph中有自己的噪声图生成&#xff09;当遇到需要噪声图时去寻找很麻烦&#xff0c;所以从网上查阅资料编写了一个Unity扩展的噪声图生成。 Perlin噪声 Perlin噪声是一种渐变噪声算法&#xff0c;由Ken …

【面试】 redis击穿现象?如何防止击穿?

文章目录 背景击穿案例解决方案:通过synchronized双重检查机制&#xff1a;某个key只让一个线程查询&#xff0c;阻塞其它线程设置value永不过期&#xff08;设置热点数据永不过期&#xff09;使用互斥锁(mutex key) 背景 大家都知道,计算机的瓶颈之一就是IO,为了解决内存与磁…

组件化开发复习

1.vue的根组件使用 // 1.创建appconst app Vue.createApp({// data: option apidata() {return {message: "Hello Vue",counter: 0,counter2: 0,content: ""}},watch: {content(newValue) {console.log("content:", newValue)}}}) createApp 函…

C#之事件

目录 一、发布者和订阅者 &#xff08;一&#xff09;概述 &#xff08;二&#xff09;有关事件的重要事项 &#xff08;三&#xff09;有关事件的私有委托需要了解的重要事项 二、源代码组件概览 三、声明事件 事件是成员 四、订阅事件 五、触发事件 六、标准事件的…

分析-WinHttpReceiveResponse失败问题追踪

Windows中的WinHttp库提供了比较完善的访问HTTP资源的接口API&#xff0c;一次在使用WinHTTP爬取QQ邮箱过程中&#xff0c;WinHttpReceiveResponse的调用总是失败&#xff0c;于是对此问题进行跟踪。 开始分析QQ邮箱的HTTP交互协议时&#xff0c;用到了代理工具Fiddler&#xf…

t.einsum(‘ijk,jkl->ijl‘, [a,b])

这个东西虽然计算起来真的方便的很多&#xff0c;但是对于人的理解难度是真的加大的&#xff0c;特别是高纬度的时候&#xff0c;例如&#xff1a;t.einsum(‘ijk,jkl->ijl’, [a,b])三维计算的时候。因此&#xff0c;最好的方法就是举个例子并且换一种方式来实现相同的功能…

安卓开发--4步实现Menu菜单动态显示隐藏

MenuInflater用法_韦_恩的博客-CSDN博客MenuInflater是用来加载menu布局文件的.应用程序运行时会预先加载资源中的布局文件&#xff0c;如果Menu布局中的资源比较多&#xff0c;会影响性能&#xff0c;所以可以选择MenuInflater方式用的时候加载&#xff0c;这样减轻了应用程序…

C语言通讯录

在本博客中&#xff0c;我们将介绍如何使用C语言构建一个基本的通讯录。主要涉及C语言的指针、结构体、动态内存管理、文件操作等方面的知识。我们还将学习如何使用C语言的各种功能和技巧来实现通讯录的各种操作&#xff0c;如添加联系人、编辑联系人、删除联系人和搜索联系人等…

并发与并行的区别(详细介绍)

并发和并行的区别为&#xff1a;意思不同、侧重不同、处理不同。 一、意思不同 1、并发&#xff1a;并发是指两个或多个事件在同一时间间隔发生&#xff0c;把任务在不同的时间点交给处理器进行处理。在同一时间点&#xff0c;任务并不会同时运行。 2、并行&#xff1a;并行…

【uniapp】更改富文本编辑器图片大小

代码块 //<view v-html"productDetails"></view><rich-text :nodes"productDetails"></rich-text>// 假设htmlContent字段是后台返回的富文本字段var htmlContent res.result.productDetailsconst regex new RegExp(<img, gi…

macOS Big Sur 11.7.9 (20G1426) 正式版 ISO、PKG、DMG、IPSW 下载

macOS Big Sur 11.7.9 (20G1426) 正式版 ISO、PKG、DMG、IPSW 下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Window…

nginx怎么做负载均衡

Nginx怎么做负载均衡 Nginx 是一个高性能的开源反向代理服务器&#xff0c;可以用于实现负载均衡。负载均衡指的是将用户请求平均分配给多个服务器&#xff0c;以提高整体系统性能和可靠性。下面是一个详细介绍如何使用 Nginx 实现负载均衡的步骤&#xff1a; 步骤 1&#xf…

vue项目打包成App

地址一 地址二 一、将项目开发完成后&#xff0c;在vue.config.js 文件中添加路径 publicPath:‘./’ 在router/index.js关闭路由的history模式&#xff08;默认哈希&#xff09; 二、npm run build&#xff0c;生成的dist文件目录 三、打开 HBuilder X 开发工具 新建 >…

线性代数(基础篇):第一章:行列式 、第二章:矩阵

文章目录 线性代数0&#xff1a;串联各章等价条件 第1章 行列式1.行列式的定义(1)行列式的本质定义(2)行列式的逆序数法定义(3)行列式的展开定理 (第三种定义) 2.行列式的性质3.行列式的公式4.基本行列式(1)主对角线行列式(2)副对角线行列式(3)拉普拉斯行列式(4)范德蒙德行列式…

SpringBoot项目——springboot配置Tomcat两个端口,https和http的方式 jar的打包和运行

目录 引出springboot配置Tomcat两个端口&#xff0c;https和http的方式1.生成SSL证书2.配置client.p12和https端口3.配置http的8080端口WebServerFactoryCustomizer接口4.启动项目 项目应用&#xff1a;在某项目中有一个功能需要https协议Tomcat启动https和http两个端口根据htt…

深度学习中标量,向量,矩阵和张量

1.标量(Scalar) 只有大小没有方向&#xff0c;可用实数表示的一个量 2.向量(Vector) 可以表示大小和方向的量 3.矩阵(Matrix) m行n列,矩阵中的元素可以是数字也可以是符号&#xff0c;在深度学习中一般是二维数组 4.张量(Tensor) 用来表示一些向量、标量和其他张量之间的…

Vue3使用Pinia-store选项式api和组合式api两种使用方式-快速入门demo

Pinia官方文档 选项式api /** Author: Jackie* Date: 2023-06-25 09:58:10* LastEditTime: 2023-07-24 17:32:25* LastEditors: Jackie* Description: pinia* FilePath: /vue3-demo-pinia/src/store/counter.js* version:*/ import { defineStore, storeToRefs } from pinia;…

el-table 表头设置渐变色

<el-table :data"tableData" stripe><el-table-column prop"name" label"测试" align"left"></el-table-column><el-table-column prop"code" label"测试1" align"left"></…