【C语言】程序环境,预处理,编译,汇编,链接详细介绍,其中预处理阶段重点讲解

目录

程序环境

翻译环境

1. 翻译环境的两个过程

2. 编译过程的三个阶段 

执行环境 

预处理(预编译) 

1. 预定义符号

2. #define 

2.1 用 #define 定义标识符(符号)

2.2 用 #define 定义宏 

2.3 #define 的替换规则 

2.4 # 和 ## 的用法

2.5 宏和函数

2.6 #undef

3. 命令行定义

4. 条件编译

5. 文件包含

5.1 两种头文件的包含

5.2 嵌套文件包含


程序环境

在ANSI C(标准C)的任何一种实现中,存在两个不同的环境。

1. 翻译环境,在这个环境中源代码被转换为可执行的机器指令。

2. 执行环境,它用于实际执行代码。

.

我们写出的C语言代码是文本信息,计算机不能直接理解,计算机是执行二进制指令的,翻译环境负责将C语言代码转成二进制指令,执行环境负责执行二进制代码。


翻译环境

1. 翻译环境的两个过程

1. 一个工程可以有多个.c(源文件)文件,每个源文件都会单独经过编译器处理生成自己对应的目标文件(.obj),这个过程叫做编译。

2. 多个目标文件和链接库经过链接器的处理,最后生成可执行程序,这个过程叫做链接。

链接库的意思是链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

2. 编译过程的三个阶段 

翻译环境分为编译和链接两部分,编译又有预处理,编译,汇编三个阶段。

1. 预处理:gcc -E test.c -o test.i

预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。

2. 编译:gcc -S test.c

编译完成之后就停下来,结果保存在test.s中。

3. 汇编:gcc -c test.c

汇编完成之后就停下来,结果保存在test.o中。

.

符号

1. 查看符号,这些符号都是全局的。

2. 每个源文件自己编译阶段符号汇总,汇编阶段形成符号表(符号,对应地址),链接进行所有源文件的符号表合并(相同符号合并,函数定义的符号的地址为有效地址,函数声明的符号的地址为无效地址,选取有效地址)。

比如:下图main.c符号表中_sum地址是无效的,合并的时候选择sum.c符号表的_sum地址。


执行环境 

程序执行的过程:

1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排(例如单片机的烧录),也可能是通过可执行代码置入只读内存来完成。

2. 程序的执行便开始。接着便调用main函数。 

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

4. 终止程序。正常终止main函数;也有可能是意外终止。


预处理(预编译) 

1. 预定义符号

以下预定义符号会在预处理阶段被替换。

__FILE__ 替换为当前进行编译的源文件名称

__LINE__ 替换为当前的行号

__DATE__ 替换为文件被编译的日期

__TIME__ 替换为文件被编译的时间

__STDC__ 如果编译器遵循ANSI C,其值为1,否则未定义

.

2. #define 

2.1 用 #define 定义标识符(符号)

语法:

#define name stuff

例子:

用 MAX 代表1000。

#define MAX 1000

为 register 这个关键字,创建一个简短的名字。

#define reg register

用更形象的符号来替换一种实现。

#define do_forever for(;;)

在写 case 语句的时候自动把 break 写上。

#define CASE break;case

如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。

#define DEBUG_PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,\__DATE__,__TIME__ )

提问:在define定义标识符的时候,要不要在最后加上;

答:不会直接报错,但没必要加,因为

1. 这只是单纯的替换,如果你加了分号,万一代码那边也写了分号,就会出现两个分号。

2. 不方便进行运算和逻辑执行,替换后你多一个分号有时候会影响原先代码的逻辑。

2.2 用 #define 定义宏 

语法:

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

#define name(parament-list) stuff 

用法:

#define MAX(x, y) (x>y ? x : y)

这个写法其实不够严谨,因为传进来的可能是多项式,所以尽量加上括号。

#define MAX(x, y) ((x)>(y) ? (x) : (y))

这个替换会发生在预处理阶段。


带副作用的宏参数 

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

例子

#define MAX(a, b)  ( (a) > (b) ? (a) : (b) )x = 5;
y = 8;
z = MAX(x++, y++);

z 就会替换为 z = ( (x++) > (y++) ? (x++) : (y++));

副作用就是表达式求值的时候出现的永久性效果。

2.3 #define 的替换规则 

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

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

2. 然后参数被宏的值所替换。

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

注意:

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

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

2.4 # 和 ## 的用法

1. # 的用法:把宏的参数插入到字符串中。

例子:

#define PRINT(n) printf("the value of n is %d\n", n)int main()
{int a = 10;PRINT(a);int b = 20;PRINT(b);return 0;
}

问题:字符串中的n没有被替换。

解决办法:在 n 前面加一个 #

#define PRINT(n) printf("the value of "#n" is %d\n", n)int main()
{int a = 10;PRINT(a);int b = 20;PRINT(b);return 0;
}

题外话:字符串的双引号是可以连接的。

比如:


2. ## 的用法:可以把位于它两边的符号合成一个符号。

例子:

将 a 和 b 连起来变成 ab。

#define CAT(x, y) x##yint main()
{int ab = 10;printf("%d\n", CAT(a, b));printf("%d\n", ab);return 0;
}

2.5 宏和函数

宏的优点

1. 执行简单的运算时,选择用宏而不是函数。

原因:

用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。

函数的参数必须声明为特定的类型,宏是类型无关的,所以函数只能在类型合适的表达式上使用。

2. 宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num, type) (type*)malloc(num*sizeof(type))MALLOC(10, int);
//预处理器替换之后:
(int*)malloc(10*sizeof(int));

宏的缺点

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

2. 宏是没法调试的。

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

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


宏与函数对比


命名约定

1. 宏名全部大写

2. 函数名不要全部大写

2.6 #undef

这条指令用于移除一个宏定义。

如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

例子:

3. 命令行定义

许多 C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

例子:

 #include <stdio.h>int main(){int array [ARRAY_SIZE];int i = 0;for(i = 0; i< ARRAY_SIZE; i ++){array[i] = i;}for(i = 0; i< ARRAY_SIZE; i ++){printf("%d " ,array[i]);}printf("\n");return 0;}

编译指令:

gcc -D ARRAY_SIZE=10 programe.c

4. 条件编译

在编译一个程序的时候,条件编译指令可以将一条语句或一组语句进行编译或者放弃编译。

常见的条件编译指令:

1. 单分支的条件编译,常量表达式由预处理器求值。

 #if 常量表达式//...#endif如:
#define __DEBUG__ 1#if __DEBUG__//..
#endif

2. 多分支的条件编译

#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif

3. 判断是否被定义,各自有两种写法。

#if defined(symbol)//...
#endif#ifdef symbol//...
#endif#if !defined(symbol)//...
#endif#ifndef symbol//...
#endif

4. 嵌套指令

#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

5. 文件包含

5.1 两种头文件的包含

本地文件包含

#include "filename"

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


库文件包含

#include <filename.h>

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


题外话

1. 其实库文件包含也能用双引号,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

2. #include 指令可以使另外一个文件被编译,就像它实际出现于一样。

这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。

这样一个源文件被包含10次,那就实际被编译10次。

5.2 嵌套文件包含

comm.h 和 comm.c 是公共模块。

test1.h 和 test1.c 使用了公共模块。

test2.h 和 test2.c 使用了公共模块。

test.h 和 test.c 使用了 test1 模块和 test2 模块。

这样最终程序中就会出现两份 comm.h 的内容,这样就造成了文件内容的重复。


解决办法有两种

1. 条件编译

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

2. 头文件开头写:

#pragma once

这样就可以避免头文件的重复引入。

林宇恒/code_c - 码云 - 开源中国 (gitee.com)

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

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

相关文章

Java小白入门到实战应用教程-权限修饰符

Java小白入门到实战应用教程-权限修饰符 前言 在前面的内容中我们其实已经接触到了权限修饰符&#xff1a;public 在java中权限修饰符除了public外&#xff0c;还有private、protected、默认权限。 权限修饰符可用来修饰类、成员变量、方法(函数)。 其中修饰类只能用publi…

使用Response.Write实现在页面的生命周期中前后台的交互

最近在做一个很大的查询&#xff0c;花时间很多&#xff0c; 用户会以为死掉了&#xff0c;就做了一个前后交互的&#xff0c;用于显示执行进度&#xff0c;在网上找了一下&#xff0c;这个比较合适。 主要是简单&#xff0c;大道至简 改进了一下&#xff1a;效果如下图 代码…

【数学建模】评价类模型:优劣解距离法

【数学建模】评价类模型&#xff1a;优劣解距离法 目录 【数学建模】评价类模型&#xff1a;优劣解距离法 1&#xff1a;前言 2&#xff1a;算法 1. 将原始矩阵正向化(统一为极大型) 2. 正向矩阵标准化(消除量纲) 3. 计算得分并归一化 3&#xff1a;例题 4&#xff1a…

shell脚本自动化部署

1、自动化部署DNS [rootweb ~]# vim dns.sh [roottomcat ~]# yum -y install bind-utils [roottomcat ~]# echo "nameserver 192.168.8.132" > /etc/resolv.conf [roottomcat ~]# nslookup www.a.com 2、自动化部署rsync [rootweb ~]# vim rsync.sh [rootweb ~]# …

jenkins集成jmeter

jenkins 安装插件HTML Publisher startup trigger Groovy 脚本介绍 cd /app/jmeter rm -rf result.jtl jmeter.log report mkdir -p report sh /app/jmeter/apache-jmeter-5.6.3/bin/jmeter.sh -n -t test.jmx -l result.jtl -e -o ./report-n: 表示以非 GUI 模式运行 JMete…

Java每日一练_模拟面试题1(死锁)

一、死锁的条件 死锁通常发生在两个或者更多的线程相互等待对方释放资源&#xff0c;从而导致它们都无法继续执行。死锁的条件通常被描述为四个必要条件&#xff0c;也就是互斥条件、不可剥夺条件、占有并等待条件和循环等待条件。 互斥条件&#xff1a;资源不能被共享&#x…

unity中实现流光效果——世界空间下

Properties{_MainTex ("Texture", 2D) "white" {}_FlowColor ("Flow Color", Color) (1, 1, 1, 1) // 流光颜色_FlowFrequency ("Flow Frequency", Float) 1.0 // 流光频率_FlowSpeed ("Flow Speed", Float) 1.0 // 流光…

QListView实现自定义的控件展示(可以根据选中与否置顶展示)

文章目录 0 问题引入1、方案1&#xff1a;使用QListwidget自定义的widget1.1 效果1.1 思路 2、方案2&#xff1a;使用QListView自定义model自定义delegate2.1.浅谈2.2.实现 3、总结4、引用 0 问题引入 问题&#xff1a;有人问我如何实现上图的功能&#xff0c;当时我脑海里有了…

手把手使用 SVG + CSS 实现渐变进度环效果

效果 轨道 使用 svg 画个轨道 <svg viewBox"0 0 100 100"><circle cx"50" cy"50" r"40" fill"none" stroke-width"10" stroke"#333"></circle></svg>简单的说&#xff0c;就是…

【Gin】深度解析:在Gin框架中优化应用程序流程的责任链设计模式(下)

【Gin】深度解析&#xff1a;在Gin框架中优化应用程序流程的责任链设计模式(下) 大家好 我是寸铁&#x1f44a; 【Gin】深度解析&#xff1a;在Gin框架中优化应用程序流程的责任链设计模式(下)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&#xf…

计算机毕业设计Hadoop+Spark旅游景点可视化 旅游景点推荐系统 景区游客满意度预测与优化 Apriori算法 景区客流量预测 旅游大数据 景点规划

### 开题报告 **论文题目&#xff1a;** 基于Spark的旅游景点可视化系统的设计与实现 **研究背景与意义&#xff1a;** 随着旅游业的快速发展&#xff0c;人们对旅游信息的获取和处理需求越来越高。传统的旅游信息系统虽然能够提供静态的数据查询和展示功能&#xff0c;但在…

C++(区别于C的)基础内容总结

参考&#xff1a; C 教程 | 菜鸟教程 (runoob.com) 简介 C 被认为是一种中级语言&#xff0c;它综合了高级语言和低级语言的特点。 C 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C 进一步扩充和完善了 C 语言&#xff0c;最初命名为带类的C&…

Java中类装载的执行过程

类装载的执行过程 类从加载到虚拟机中开始&#xff0c;直到卸载为止&#xff0c;它的整个生命周期包括了&#xff1a;加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中&#xff0c;验证、准备和解析这三个部分统称为连接&#xff08;linking&#xff09;。 1.加载 …

学习记录(9):Prompt提示词技巧

依旧照例先感谢前辈们的慷慨分享 今天学习的是这篇文章↓ 原文&#xff1a;转自公主号“博金斯的AI笔记” —《4篇Prompt论文的提示词技巧, 3 个 GPTs 实例测试》 文章目录 一、提示词框架二、逻辑链&#xff08;Chain of thought&#xff09;三、思维树&#xff08;Tree of th…

[渗透测试学习] PermX-HackTheBox

文章目录 PermX-HackTheBox信息搜集漏洞利用权限提升参考文章PermX-HackTheBox 信息搜集 nmap扫描一下端口 nmap -sC -v 10.10.11.23扫描结果如下 PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 256 e2:5c:5d:8c:47:3e:d8:72:f7:b4:80:03:49:86:6d:ef (ECDSA…

二维码直达App,Xinstall为你打通运营任督二脉

在移动互联网时代&#xff0c;App的推广和运营显得尤为重要。然而&#xff0c;许多企业在投入大量资源进行App推广和运营时&#xff0c;总会遇到一些棘手的问题&#xff0c;如用户转化率低、数据分析困难等。今天&#xff0c;我们要为大家揭秘一个神奇的助手——Xinstall&#…

Github2024-07-29 开源项目周报Top15

根据Github Trendings的统计,本周(2024-07-29统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目2Java项目2HTML项目2C项目2TypeScript项目2JavaScript项目2非开发语言项目1Vue项目1Go项目1Dart项目1C++项目1Rust项目1Jupyter Note…

【Linux】-----进度条小程序

目录 前言 基本知识 Ⅰ、回车和换行 Ⅱ、缓冲区 两个有意思的现象 简单定义 刷新缓冲区 简易倒计时程序 进度条代码 多文件下makefile写法 一代(无任何场景) procs1.h代码 procs1.c代码 主函数main1.c 一代运行结果&#xff1a; 二代 (搭配下载场景) procs2.c代…

在jmeter中使用javascript脚本

工作上遇到一个压力测试的需求&#xff0c;需要测试几个考试相关的接口。其中有一个获取试题详情的接口&#xff0c;和一个提交答题信息的接口。后一个接口以上一接口的返回内容为参数&#xff0c;添加上用户的答案即可。jmeter提供了非常多的方式可以实现该需求&#xff0c;这…

MySQL分组查询有关知识总结

目录 4. 分组查询&#xff08;group by&#xff09; 4.1 概述 4.2 分组函数 4.2.1 单个使用 4.2.2 组合使用 4.2.3 注意&#xff01; 4.3 group by 4.3.1 单个字段 4.3.2 多个字段 4.3.3 提醒&#xff01; 4.4 having 4.5 分组查询演示 4. 分组查询&#xff08;…