C语言 程序的翻译 预处理 编译 汇编 链接 #define详解
1.程序的翻译环境和执行环境
执行环境:所在操作系统的平台 win10 win11 linux
翻译环境:MSVC gcc g++
你的vs 2019 和2022 是集成开发环境把编辑器编译器全部给你集成到一块了,也就是所有的功能给你自动生成的,查看中间过程就有些困难,然而 Linux下就可以通过操作选项来一个个看到这些步骤,或者是用vs code这种编辑器配好win下的gcc就可以一步步的看到过程了。
2.翻译的过程
我们想要把一个.c文件变成一个.exe文件要经过一下过程
2.1 预处理
预处理主要干的活就是头文件展开,宏替换,去注释,条件编译。
在Linux的gcc下通过gcc -E hello.c -o hello.i 也就是开始翻译完成预处理就停下来再生成hello.i文件
预处理进行了宏替换 NUM被替换成了100,而且进行了头文件展开将你代码中调用的方法从你所在的头文件给你复制粘贴进来,然后你的注释也被去掉了,而且预处理之后还是语言
2.2编译
通过gcc -S hello.i -o hello.s开始翻译完成编译就停下来
这时候这是啥呢,他已经不是c语言了他是汇编语言,那我们的编译的过程也就是把刚刚那一份干净的纯C语言变成汇编语言。
2.3 汇编
虽然叫汇编可不是把c语言变成汇编语言,上一步已经做过了 这一步的命令行是 gcc -c hello.s -o hello.o
这里用vim看就是乱码了,那我们用这个格式化处理工具od 来查看一下
很明显发现这不是二进制吗,是的汇编的工作就是把汇编代码转换成二进制程序,但是这个是不可执行的这个以.o结尾文件叫做目标文件,那.o\文件运行不了说明还是少了一步
2.4 链接
直接gcc hello.o链接形成a.out文件./a.out就可以看见程序执行的结果了,这个printf();函数是你写的吗?不是的他是在c语言的库中。链接的作用就是把程序跟库文件相关联的过程。
3 运行环境
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止
4 预处理详解
4.1 预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
这里就能清晰的看到,被替换了。
4.2 #define
define 定义的标识符
//语法
#define NAME stuff
举个例子
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
那我们一般对于数字进行宏替换的时候要加;吗?
比如
#define MAX 1000;
#define MAX 1000
让我们gcc一下
就很明显的看到了错误
定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏。
下面是宏的申明方式:
#define name( parament-list ) stuff其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
比如这个例子
我们原本想的是应该是11*11 结果是121 为啥是21呢,那就看看.i 文件看看到底宏替换哪块出问题了
原来是替换之后根据运算符号的先后顺序所以结果是21那么怎样去避免这个问题呢我们在宏定义的时候这样去做
#define sq(x) (x)*(x)
这次是避免了那要是出现这种情况呢
不是按理来说应该是110吗咋变成1210了那我们再看看.i文件
跟刚刚一样的老问题还是顺序问题带来的那这次怎么办呢
#define sq(x) ((x)*(x))
带参宏就会有很多很多的问题在替换中提现到那只能尽可能的加()来避免操作顺序带来的问题
尽量不要使用这种带参数的
替换规则
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
4.3 #和##
我们知道临街字符串,具有自动连接的特性
使用#把一个宏参数变成字符串
使用##可以把位于它两边的符号合成一个符号。
比如这个例子
我的标识符x跟1就合体成x1了,我调用NUM就是x1
4.4带有副作用的宏
x+1;//不带副作用 我x还是没有变
x++;//带有副作用 我x自增了结果变化了
有这个例子
#include<stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main() {int x = 5;int y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);
}
输出结果就是 6 10 9 因为我在max里面做了一次++ 我宏替换换的是整个 比完大小之后我又加了一次这就是副作用带来的影响。
4.5宏和函数的对比
举个例子吧,比如在做leetcode中老想追求双百,时间哪块想更快咋办呀,可以将小型运算换成宏,调用函数和从函数返回的代码肯比较慢,而且更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于 > 来比较的类型,宏是类型无关的!
当然和宏相比函数也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
5 #undef
简而言之就是取消宏定义
6 命令行编译
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
这条gcc a.c -DNUM=100 D也就是我们的 define对NUM进行宏定义
7 条件编译
我们刚刚在预处理的时候说了那个时候会进行条件编译那这个是啥呢,其实也就是对代码进行截断比如我现在没有小月卡或者大月卡,我现在充钱了,给了这个程序一个动作,那大月卡用户可以有的资源也就可以拥有了,换个说法也就是调用那一段代码,那段代码是实现大月卡能干什么的方法。这一段被截断的代码现在就在我的总的代码里面了,一家游戏公司不可能专门为普通用户写一套代码,为大月卡写一套,在为小月卡用户写一套,这样的成本也太高了吧。
8 文件包含
我们已经知道#include 指令可以使另外一个文件被编译,就像他实际出现#include指令单地方一样。预处理先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次!
8.1头文件包含的方式
” “这样包含自己定义的头文件 首先是在当前目录下寻找找不到再在库函数目录下寻找
<>这样包含的头文件直接就在库函数目录下寻找
C语言 程序的翻译 预处理 编译 汇编 链接 #define详解相关推荐
- 【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )
相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...
- C/C++编译和链接过程详解 概述 (重定向表,导出符号表,未解决符号表)
详解link 有 些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错 ...
- arm-none-eabi-gcc编译、链接选项详解
1.-mthumb 和 -mthumb-interwork "-mthumb"的意义是:使用这个编译选项生成的目标文件是Thumb指令的,目前还没有发现GNU编译器中有哪一个选项可 ...
- 汇编 - ORG指令详解
ORG指令 ORG是Origin的缩写:起始地址,源.在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址.如果不用ORG规定则汇编得到的目标程序将从0000H开始.例如: ...
- 学C/C++想提升功底 可以花点时间看看这篇博客---C语言程序环境和预处理
本篇博客介绍了C语言程序环境和预处理.主要包含程序的翻译和运行环境和 各种预处理操作:预定义符号.各种#define 用法 undef的使用条件编译的使用 头文件包含指令 C语言程序环境和预处理 一. ...
- 51汇编与c语言混合编程,C51与汇编混合编程详解
C51与汇编混合编程详解 0750long | 2009-07-09 12:45:42 阅读:1257 发布文章 C51与汇编混合编程详解 C51和汇编混合编程(1)-C语言中嵌入汇编 1.在 ...
- 【转】Android APK反编译就这么简单 详解(附图)
转自:http://blog.csdn.net/vipzjyno1/article/details/21039349/ [置顶] Android APK反编译就这么简单 详解(附图) 分类: and ...
- 电大计算机C语言1253,1253《C语言程序设计》电大期末精彩试题及其问题详解
1253<C语言程序设计>电大期末精彩试题及其问题详解 (34页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 实用文档&l ...
- 使用VS2010编译MongoDB C++驱动详解
最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.h ...
最新文章
- 图片格式转换(PNG or JPEG to EPS or PDF)
- php记录用户搜索历史记录,PHPCookei记录用户历史浏览信息的代码
- 5.1 Tensorflow:图与模型的加载与存储
- WinForm中 事件 委托 多线程的应用【以一个下载进度条为例】
- php 多进程 常驻内存,PHP 多进程与信号中断实现多任务常驻内存管理 [Master/Worker 模型]...
- IBM软件服务创新运用 提升市民生活质量
- Unity3d之HashSlash学习笔记之(二)--角色基础类的构建
- 浮栅场效应管 符号_MOS场效应管
- 二.Windows I/O模型之异步选择(WSAAsyncSelect)模型
- python-gui-pyqt5的使用方法-8--实际案例可参考使用
- 论SetItemData和GetItemData
- php_eol为什么没有换行,PHP PHP_EOL 换行符
- matlab绘制二元一次函数图像_二元一次函数曲线拟合的Matlab实现.pdf
- 编辑为什么建议转投_编辑建议转投更合适的期刊_建议改投其他期刊是什么意思_改革期刊投稿要求...
- Unity 利用射线实现弹孔效果
- word强调文字颜色在哪,强调文字颜色2 word2010如何将文字设置成红色,强调文
- 真我Realme GT Neo5有无线充电吗? 真我Realme GT Neo5快充速度是多少瓦?
- 支付宝扫五福,你扫了吗
- SpringBoot+Beetlsql代码生成
- h.265/HEVC解码器verilog实现