C语言八股---预处理,编译,汇编与链接篇
前言
从多个.c文件到达一个可执行文件的四步:
预处理–>编译–>汇编–>链接
预处理

预处理过程就是预处理器处理这些预处理指令(要不然编译器完全不认识),最终会生成 main.i的文件
主要做的事情有如下几点:
- 展开头文件
- 展开宏
- 条件编译
- 删除注释
- 添加行号等信息
- 保留parama预处理指令
- 头文件展开—#include指令
- #include <sdtio.h> 和 #include “stdio.h”
对于<> 搜索顺序为- 通过GCC参数gcc-I指定的目录(注:大写的I 让我们自由指定的)。
- 通过环境变量CINCLUDEPATH指定的目录。
- GCC的内定目录。
对于 " "的搜索时顺序 - 项目当前目录(此时也可以用"…/LED/led.h"方式去搜索)
- 通过GCC参数gcc-I指定的目录。
- 通过环境变量CINCLUDEPATH指定的目录。
- GCC的内定目录
- 为什么把声明放在头文件里
- 提供一个接口 方便其他文件通过声明调用对应的函数
- 当我们的led.c 包含了 led.h的时候 也方便编译器做类型的检查
- 头文件多次包含会增加可执行文件的体积吗?
只要是使用了类似#pragma once 或者#ifndef 多次包含是不会的增加可执行文件的体积的
同样的要注意:声明不会增加可执行文件的体积
- #include <sdtio.h> 和 #include “stdio.h”
- 宏展开#define 宏指令
- 宏定义最小
#define MIN(x,y) ((x) > (y) ? (y) : (x))
因为宏只是做了一个替换所以对于如下代码
- 宏定义最小
#include <stdio.h>#define MIN(x,y) ((x) > (y) ? (y) : (x))//因为宏只是做了一个替换所以对于如下代码int main(){int a = 2 ;int b = 5;int c = MIN(a++,b++);printf("c = %d a = %d b = %d\r\n",c,a,b); // a竟然等于4}
在这里可以用GNU C语法中的一些小技巧操作
#define MIN(x,y) ({\typeof(x) _x = (x);\typeof(y) _y = (y);\_x > _y ? _x : _y;})
- 定义一个很大的常数的时候
#define MAX_LONG (100001000010000)UL //指定类型 - ##连接符
高端用法 看了好多代码都用这个 但是分析起来乱乱的,大概就是把两个字母连接到一起
#define contact(x,y) (x##y)int bc = 50;printf("bc = %d\r\n", contact(b,c));
- offset_of与container_of
之前写结构体的时候写过,权当复习一下#define offset_of(type, member) ((size_t)(&((type *)0)->member)) #define container_of(type,member,ptr) (type *)((size_t) ptr - offset_of(type,member)) struct student {int height;char * name; }; int main() {struct student stu;stu.height = 50;stu.name = "123456";char ** tmp_name = &stu.name;struct student* s = &stu;printf("%p %p %ld\r\n",s, tmp_name,offset_of(struct student,height));struct student *new_s = container_of(struct student,name,tmp_name);new_s->height = 60;printf("%d\r\n",stu.height); }
``
- 宏为什么要用 do {} while(0)
如果去看linux源码也好还是RTOS等的代码也好 会有很多时候用到do_while(0) 它的作用是什么呢
假设我们定义了
#define MACRO() foo(); bar()
此时我们写了这样的伪代码
if (condition)
MACRO();
else
baz();
// 宏展开后和我们想要的就完全不一样了 直接就出错了
// do {}while(0)可以保证宏作为一个整体执行 此时就可以定义一些局部变量
- 条件编译 #ifdef等指令
-
条件编译指令

正常用的比较多的就是 #ifndef #define #endif这几个连用
也有 #defined(VAR_X)之类的 -
#error指令
如果发生错误直接中断编译过程

- #pragma 指令
- #pragma pack([n]):指示结构体和联合成员的对齐方式。
- #pragma message(“string”):在编译信息输出窗口打印自己的文
本信息。 - #pragma warning:有选择地改变编译器的警告信息行为。
- #pragma once:在头文件中添加这条指令,可以防止头文件多次
编译。
编译
真要讲编译我也是不配讲的 就我们知道这是在干嘛就行了
编译就是把.c文件变成汇编文件的过程
- 编译过程的6步
词法分析 / 语法分析 / 语义分析 / 中间代码生成 / 汇编代码生成 / 目标代码生成- 语法错误: stynax error: 缺少分号 / {}没扩住 /
- 语义错误: 类型不匹配 未定义的变量
最终的结果就是生成.S文件
- gcc的优化等级 gcc -O
可以参考
https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options - 交叉编译
嵌入式开发板一般是ARM架构 然后PC是x86架构
通过交叉编译器进行程序的编译
汇编
汇编就是把汇编代码编程机器码 也就是比较熟悉的 xx.o文件了
汇编过程最终会生成以零地址为链接起始地址的可重定位目标文件
链接
把.o 文件进行组装 需要重定位 因为所有的.o文件的开头都是以0地址开头的
链接主要分为3个过程:分段组装、符号决议和重定位
-
分段组装
不太好讲 基本就是通过一个脚本把多个文件按照段组合到一起

-
符号决议
符号决议的核心就行 如果说变量/函数重名了怎么办- 不允许同时存在两个相同的强符号
初始化的全局变量、函数名默认都是强符号,未初始化的全局变量默认是弱符号
比如
- 不允许同时存在两个相同的强符号
// b.cint i; // 未初始化 是弱符号int main() {printf("%d\r\n",i); //i的值是20}// a.cint i = 20;
__attribute__关键字 可以把强符号强行转换为弱符号 attribute((weak))
- 使用弱符号的好处
-
自定义重名函数
这里在嵌入式里最常见的就是中断服务函数的弱定义了
当我们需要重新定义中断服务函数的时候 只需要保证名字很start.S的名字一致就行,链接的时候就知道链接到哪里了

-
检查该函数是否存在
// b.c
#include <stdio.h>
int global_k;
char global_i;
attribute((weak)) void func()
{
printf(“这被定义为弱符号了\r\n”);
}
int main()
{
printf(“%d\r\n”,global_k);
if(func)
func(); //调用的是强符号的函数
return 0;
}
// a.c
#include <stdio.h>
int global_k = 20;
int global_i;
void func()
{
printf(“这被定义为强符号了 fun\r\n”);
}
-
- 同样都是弱符号 谁体积大谁胜出
- 重定位
因为要把不同的文件链接到一块 所以位置就会发生变化
相关文章:
C语言八股---预处理,编译,汇编与链接篇
前言 从多个.c文件到达一个可执行文件的四步: 预处理–>编译–>汇编–>链接 预处理 预处理过程就是预处理器处理这些预处理指令(要不然编译器完全不认识),最终会生成 main.i的文件 主要做的事情有如下几点: 展开头文件展开宏条件编译删除注释添加行号等信息保留…...
平衡二叉树(AVL树)
平衡二叉树是啥我就不多说了,本篇博客只讲原理与方法。 首先引入平衡因子的概念。平衡因子(Balance Factor),以下简称bf。 bf 右子树深度 - 左子树深度。平衡结点的平衡因子可为:-1,0,1。除此…...
SpringBoot(一)--搭建架构5种方法
目录 一、⭐Idea从spring官网下载打开 2021版本idea 1.打开创建项目 2.修改pom.xml文件里的版本号 2017版本idea 二、从spring官网下载再用idea打开 三、Idea从阿里云的官网下载打开 编辑 四、Maven项目改造成springboot项目 五、从阿里云官网下载再用idea打开 Spri…...
RabbitMQ使用延迟消息
RabbitMQ使用延迟消息 1.什么情况下使用延迟消息 延迟消息适用于需要在一段时间后执行某些操作的场景,常见的有以下几类: 1.1. 订单超时取消(未支付自动取消) 场景: 用户下单后,如果 30 分钟内未付款&a…...
MyBatis-Plus 分页查询接口返回值问题剖析
在使用 MyBatis-Plus 进行分页查询时,很多开发者会遇到一个常见的问题:当分页查询接口返回值定义为 Page<T> 时,执行查询会抛出异常;而将返回值修改为 IPage<T> 时,分页查询却能正常工作。本文将从 MyBatis-Plus 的分页机制入手,详细分析这一问题的根源,并提…...
DeepLabv3+改进7:在主干网络中添加SegNext_Attention|助力涨点
🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 论文简介 近期有关移动网络设计…...
c语言笔记 内存管理之栈内存
物理内存和虚拟内存 在c语言的程序需要内存资源,用来存放变量,常量,函数代码等,不同的内容存放在不同的内存区域,不同的内存区域有着不同的特征。 c语言的每一个进程都有着一片结构相同的 虚拟内存,虚拟内…...
分布式事务的原理
文章目录 基于 XA 协议的两阶段提交(2PC)三阶段提交(3PC)TCC(Try-Confirm-Cancel)Saga 模式消息队列(可靠消息最终一致性) 分布式事务是指在分布式系统中,涉及多个节点或…...
鸿基智启:东土科技为具身智能时代构建确定性底座
人类文明的每一次跨越都伴随着工具的革新。从蒸汽机的齿轮到计算机的代码,生产力的进化始终与技术的“具身化”紧密相连。当大语言模型掀起认知革命,具身智能正以“物理实体自主决策”的双重属性重新定义工业、医疗、服务等领域的运行逻辑。在这场革命中…...
SQL29 计算用户的平均次日留存率
SQL29 计算用户的平均次日留存率 计算用户的平均次日留存率_牛客题霸_牛客网 题目:现在运营想要查看用户在某天刷题后第二天还会再来刷题的留存率。 示例:question_practice_detail -- 输入: DROP TABLE IF EXISTS question_practice_detai…...
MWC 2025 | 移远通信推出AI智能无人零售解决方案,以“动态视觉+边缘计算”引领智能零售新潮流
在无人零售市场蓬勃发展的浪潮中,自动售货机正经历着从传统机械式操作向AI视觉技术的重大跨越。 移远通信作为全球领先的物联网整体解决方案供应商,精准把握行业趋势,在2025世界移动通信大会(MWC)上宣布推出全新AI智能…...
sparkTTS window 安装
下载 Spark-TTS Go to Spark-TTS GitHubClick "Code" > "Download ZIP", then extract it. 2. 建立 Conda 环境 conda create -n sparktts python3.12 -y conda activate sparktts 3. Install Dependencies pip install -r requirements.txt In…...
数据库原理6
1.数据是信息的载体 2.数据库应用程序人员的主要职责:编写应用系统的程序模块 3.关系规范化理论主要属于数据库理论的研究范畴 4.数据库主要有检索和修改(包括插入,删除,更新)两大操作 5.概念模型又称为语义模型。…...
接口自动化入门 —— Http的请求头,请求体,响应码解析!
在接口自动化测试中,HTTP请求头、请求体和响应码是核心组成部分。理解它们的作用、格式和解析方法对于进行有效的接口测试至关重要。以下是详细解析: 1. HTTP 请求头(Request Header) 1.1 作用 请求头是客户端向服务器发送的附加…...
tcc编译器教程6 进一步学习编译gmake源代码
本文以编译gmake为例讲解如何使用tcc进行复杂一点的c代码的编译 1 简介 前面主要讲解了如何编译lua解释器,lua解释器的编译很简单也很容易理解.当然大部分c语言程序编译没那么简单,下面对前面的gmake程序进行编译. 2 gmake源码结构 首先打开之前tcc-busybox-for-win32\gmak…...
公司共享网盘怎么建立
公司共享网盘的建立,关键在于明确使用需求、选择合适的网盘服务、搭建统一的文件管理规范、做好权限分级与安全防护。尤其要强调选择合适的网盘服务这一点,如果企业规模较大,且对协同办公的需求强烈,就需要考虑支持多人实时协作、…...
【高分论文密码】AI大模型和R语言的全类型科研图形绘制,从画图、标注、改图、美化、组合、排序分解科研绘图每个步骤
在科研成果竞争日益激烈的当下,「一图胜千言」已成为高水平SCI期刊的硬性门槛——数据显示很多情况的拒稿与图表质量直接相关。科研人员普遍面临的工具效率低、设计规范缺失、多维数据呈现难等痛点,因此科研绘图已成为成果撰写中的至关重要的一个环节&am…...
深入理解Java中的static关键字及其内存原理
static是Java中实现类级共享资源的核心修饰符,它突破了对象实例化的限制,使得变量和方法能够直接与类本身绑定。这种特性让static成为构建工具类、全局配置等场景的利器,但同时也带来独特的内存管理机制需要开发者关注。 static修饰成员变量…...
linux 系统 之centos安装 docker
对于 CentOS 安装 Docker 的前置条件 首先,需要安装一些必要的软件包, 对于 CentOS 7,可以使用以下命令: sudo yum install -y yum-utils device-mapper-persistent-data lvm2添加 Docker 仓库 设置 Docker 的官方仓库。对于 …...
Python语法核心架构与核心知识点:从理论到实践
一、Python的核心设计哲学 Python以“简洁优雅”为核心理念,遵循以下原则: # Zen of Python(输入 import this 可查看) >>> import this The Zen of Python, by Tim Peters ... Simple is better than complex. Readab…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...
