嵌入式笔记(入门系列2)
目录
宏函数
预处理器#include
内存泄漏
内存对齐
堆与栈
Malloc 和 New
Inline
宏函数
宏函数,宏函数,实际上就是让宏像函数一样被使用。宏函数以函数形式的方式进行入参,但是返回结果是通过表达式求值得到。话说的抽象,我们来实际分析一个。
#define squared(num) ( ( num ) * ( num ) ) int main(){return squared(5); }
可以看到当我们预处理文件结束后,程序自动被替换为:
int main(){return 25; }
也就是说,我们的宏函数的运行结果将会在编译期间就得到了结果,将处理的结果直接cv到调用处,嗨!这个是一个麻烦的事情。
考虑一个经典的问题:
#define wrong_sqr(X) X * X int main() {return wrong_sqr(3 + 2) }
调用方预期的是——调用的是(3 + 2)^2
,但是却得到了11,这是为何呢?回到我上面加粗的那句话,直接替换到调用处,由于我们没有对X添加括号表达优先级,导致产生的奇奇怪怪的bug
int main() {return 3 + 2*3 + 2; }
很好,令人烦闷,所以我不得不这样做:
#define sus_sqr(X) (X) * (X) int main() {return sus_sqr(3 + 2) }
看起来没问题了吗?这个例子看起来没问题了,但是我们仍然推介在最外层再次添加一个()
,保证我们的计算单元是独立的
#define squared(num) ( ( num ) * ( num ) ) int main(){return squared(5); }
也就是我们最开始的这个写法。
上面这个例子已经让我们看到宏函数之用处。这不禁想让人问:他跟函数调用有什么区别呢?
首先,在没有展开优化的情况下,宏函数毫无疑问的相对函数调用开销小。思考一下,函数调用需要首先将参数mov进入寄存器,然后jmp到对应的函数入口,做参数入栈准备,计算结束后又弹栈jmp返回地址。对于宏函数,则是把事情放到编译期间,做全局的直接替换得到。这个事情有点模板元编程的味道了,当然这就跟我们的主题相去甚远了。
但是,宏函数毛病很大,如你所见,为了保证优先级,我们需要思考添加很多次()
保证不会出现优先级计算乱套,如果我们的参数有很多,那该怎么办呢?因此,在今天编译器优化愈发良好的今天,不推介使用宏函数来提升性能。
对于最新版本的编译器,没必要做任何更改,编译器自动裁决判定函数是否可以直接内联减少开销。(开优化,如果不开的话对于GCC考虑使用force-inline强制内联)
老旧的办法是这样的
inline double MAKE_SQUARE(x) {return x * x}
添加尚未变化含义的inline来指示编译器采取内联行动。
对于C++用户,考虑使用C++11以上的constexpr来采取常量表达式求解,从而获取跟宏函数一样卓越的性能,同时降低心智开销。
static constexpr SQUARE(X) {return X * X;}
预处理器#include
预处理器是预处理阶段下指挥编译器干活的几个指令,不同编译器对大大小小的预处理器可能略有不同,笔者比较熟知的就是内存对齐的开始和结束中,msvc和gcc的就不一样。具体的可以参考撰写《高效C/C++调试》的大佬的详细阐述。但是,常见的#define, #include
等等,行为完全按照标准进行。
#include做的事情实在是简单:直接将文件cv到#include的地方。如果看官想要求证,笔者推介您直接尝试
#include <stdio.h> int main(){ }
就这样放着,然后使用编译器只做预处理,你就会高兴的发现自己的代码多出了一大堆,仔细一瞧就是文件咔的一下贴在了文件的第一行——哈哈,这下也成写几千行代码的人了,可惜是CV出来的。
常见的#include有两种格式:
#include <stdio.h> // I #include "CCVector.h" // II
很好,区别是什么呢,编译器搜索路径的优先级不同。同志们都知道:编译器对于<>
包含的文件,优先按照如下路径扫描:
-
在编译器设置的include路径内搜索
-
在全局的系统路径下查找
-
最后,不舍的看一眼自己的工程目录看看(不递归查找)没有就给你抛错误:No Such File Or Directary
也就是说,我们认为,#include<>
用在使用了标准库和第三方库的时候,使用比较高效,编译器将会优先在外部寻找,提升查找效率
那#include""
如何呢?很简单,那就是优先看看自己家的工程目录,也就是说,对于隶属于项目自身的模块查找最高效!
内存泄漏
HOLY SHIT!看到这个就会不自觉的头大,这个问题在没有自动构造与析构的C语言下简直就是一场灾难(当然是标准通用的,GCC存在辅助析构构造的mark),任何编写或者使用过大型库的同志们不可避免的出现过这样的代码范式:
... ptr->do_init(); ... ptr->erase_self();
这中间就会申请释放资源,如果我们忘记,那就会出现一些在堆上申请的资源没有被释放,咋着?内存泄漏了,程序没办法追踪这些撒把的内存了
最快的内存泄漏方式是这样的:
int main() {{void* block = malloc(sizeof(byte));} }
你会发现在{}
外面完全没办法拿到地址了,block的内容已经被销毁覆盖了。
所以,请务必保证自己创建的每一个对象都有始有终!
内存对齐
这个玩意有趣,内存对齐实际上最广泛的用在通信协议设计上,我们都知道,一个结构体内部各成员都是按照所有成员的最大字节数对齐的,举个例子
struct PlainBuffer{char data1;int data2;double data3; }
-
第一个成员在与结构体变量偏移量为0的地址处。(即结构体的首地址处,即对齐到0处)
-
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
-
结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
-
如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
不用猜,就是16个字节大小。
data1对齐到0地址字节处,而data2则是对齐到后面的4的位置,data3对齐到7的位置,总共占据16个字节,没毛病!这样做的原因是提升取数的效率,是一个经典的空间换时间的操作。
另外,#pragma pack
可以调整结构体对齐方式,请参考这位博主写的:结构体内存对齐(如何计算结构体的大小)_按照结构体在内存中的对齐规则,下列结构体类型变量占用内存的大小为( ) struct te-CSDN博客
堆与栈
我留意到一些博主很贴心的阐述了堆与栈在计算机操作系统层次和数据结构层次的区别。在数据结构上我们谈到堆与栈还是说的是大小根堆(树的变种)和FIFO栈。这里我们重点阐述的是进程运行期间的内容。
嘿!这里的栈的工作方式跟FIFO栈的一致,都是FIFO的结构,由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等。
堆就是存放那些希望超越局部变量的,希望声明周期可以动态的调整了的数据的地方,由我们手动分配和释放, 若开发人员不释放,程序结束时由操作系统回收,分配方式类似于链表。
Malloc 和 New
C/C++双修的人可以很快的Get到他们的区别。我们知道,C++是一个重视面对对象的程序语言。讲求将对象按照内存分派 + 初始化的方式进行构造,结束后进行结束操作 + 内存销毁。这就出来了:Malloc是New的第一步!
new T <-> malloc(sizeof(T)), T(); delete T <-> ~T(), free(T)
笔者推介程序设计的时候也仿照着来,这个顺序下来程序不容易出错。
Inline
浅谈 C++ 中的 inline (1) - 知乎 (zhihu.com)
笔者强推这篇文章,做了求证。这里简单的结合我的经验谈谈。
首先,在编译器尚不完备的过去,编译器需要听从程序员的指令对函数进行可能的内联操作,伴随着编译器的进步,越来越多的编译器在一定的现代C/C++语境下,开始使用自己的估算程序估算内联是否可以带来内联操作。因此,inline在今天慢慢退步成拥有其他含义的关键字,就比如说弱链接(__weak?)的事情。不过这个事情,如您所见,不同编译器的行为并不一致,因此遵循STD C是一个比较好的选择——即声明 + 实现同时存在的时候,函数体短小精悍的时候使用inline,笔者喜欢这样使用:
// In Module: Debug Print static void __pvt_debug_print_impl(){printf("Debug!");// ... } static void __pvt_release_print_impl(){printf("Release");// ... } static inline void __pvt_print(){ #ifdef DEBUG__pvt_debug_print_impl(); #else__pvt_release_print_impl(); #endif } void Module_Tell_Mode() {__pvt_print();// ... }
更多的,梭哈cppref: inline specifier - cppreference.com
相关文章:
嵌入式笔记(入门系列2)
目录 宏函数 预处理器#include 内存泄漏 内存对齐 堆与栈 Malloc 和 New Inline 宏函数 宏函数,宏函数,实际上就是让宏像函数一样被使用。宏函数以函数形式的方式进行入参,但是返回结果是通过表达式求值得到。话说的抽象,我…...

并发编程多线程
1.线程和进程的区别? 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间线程更轻量,线程上下文切换成本一般上要比进程上下文…...

【十八】MySQL 8.0 新特性
MySQL 8.0 新特性 目录 MySQL 8.0 新特性 概述 简述 1、数据字典 2、原子数据定义语句 3、升级过程 4、会话重用 5、安全和账户管理 6、资源管理 7、表加密管理 8、InnoDB增强功能 9、字符集支持 10、增强JSON功能 11、数据类型的支持 12、查询的优化 13、公用…...

巨潮股票爬虫逆向
目标网站 aHR0cDovL3dlYmFwaS5jbmluZm8uY29tLmNuLyMvSVBPTGlzdD9tYXJrZXQ9c3o 一、抓包分析 请求头参数加密 二、逆向分析 下xhr断点 参数生成位置 发现是AES加密,不过是混淆的,但并不影响咱们扣代码 文章仅提供技术交流学习,不可对目标服…...

传知代码-从零开始构建你的第一个神经网络
代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 从零开始构建你的第一个神经网络 在本教程中,我们将使用PyTorch框架从零开始构建一个简单的卷积神经网络(CNN),用于图片二分类任务。CNN 是一种深度学习模型&#…...

大厂面试真题:SpringBoot的核心注解
其实理解一个注解就行了@SpringBootApplication,我们的启动类其实就加了这一个 但是这么答也不行,因为面试官要的答案肯定不止这一个 我们打开SpringBootApplication的源码,会发现上面加了一堆的注解 相对而言比较重要是下面三个…...

Java设计模式—面向对象设计原则(五) ----->迪米特法则(DP) (完整详解,附有代码+案例)
文章目录 3.5 迪米特法则(DP)3.5.1 概述3.5.2 案例 3.5 迪米特法则(DP) 迪米特法则:Demeter Principle,简称DP 3.5.1 概述 只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to stranger…...
docker多阶段镜像制作,比如nginx镜像,编译+制作
镜像制作, nginx的源码包 把nginx源码拷贝到容器内 编译要用到gcc make , 以及扩展工具 pcre openssl # "pcre" perl compatibal regulaer expression 刚开始,可以两个终端, 一个手工操作(编译安装、拷贝、环境变量等)…...
大语言模型量化方法GPTQ、GGUF、AWQ详细原理
大语言模型量化的目的是减少模型的计算资源需求和存储占用,同时尽量保持模型的性能。以下是几种常见的量化方法的原理; 1. GPTQ (Gradient-based Post-training Quantization) GPTQ 是一种基于梯度的后训练量化方法,主要目的是在减少浮点计…...
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
摘要 本文深入探讨了 AVL 树(自平衡二叉搜索树)的概念、特点以及实现细节。我们首先介绍了 AVL 树的基本原理,并详细分析了其四种旋转操作,包括左旋、右旋、左右双旋和右左双旋,阐述了它们在保持树平衡中的重要作用。…...
SAP 特别总账标识[SGL]
1. 特别总账标识(SGL)概述 1.1 定义与目的 特别总账标识(Special General Ledger, SGL)在SAP系统中用于区分客户或供应商的不同业务类型,以便将特定的业务交易记录到非标准的总账科目中。 定义:SGL是一个用于标记特殊业务类型的…...

认知杂谈77《简单:通往高手的技巧》
内容摘要: 在信息爆炸、关系复杂的时代,简单是复杂背后的真谛。简单如“112”,是智慧的朴素呈现。简单有强大力量,像清泉般纯净,如“我爱你”简单却有力,基础财务知识也体现其在理财中的作…...

《SmartX ELF 虚拟化核心功能集》发布,详解 80+ 功能特性和 6 例金融实践
《SmartX ELF 虚拟化核心功能集》电子书现已发布!本书详细介绍了 SmartX ELF 虚拟化及云平台核心功能,包含虚机服务、容器服务、网络服务、存储服务、运维管理、工具服务、数据保护等各个方面。 即刻下载电子书,了解如何利用基于 SmartX ELF …...

9月23日
思维导图 作业 统计家目录下.c文件的个数 #!/bin/bashnum0for file in ~/*.c; doif [ -f "$file" ]; then((num))fi doneecho "家目录下.c文件的个数: $num"...
如何使用Jinja定义dbt宏
dbt宏在dbt框架内的工作方式与传统编程中的函数类似。它允许用户将特定的、通常是重复的SQL逻辑封装到可调用的命名单元中,就像在其他编程语言中用函数来避免重复代码一样;dbt宏定义特定业务的SQL逻辑,然后在dbt项目中需要的地方调用该宏函数…...

深入理解 JavaScript 三大作用域:全局作用域、函数作用域、块级作用域
一. 作用域 对于多数编程语言,最基本的功能就是能够存储变量当中的值、并且允许我们对这个变量的值进行访问和修改。那么有了变量之后,应该把它放在哪里、程序如何找到它们?是否需要提前约定好一套存储变量、访问变量的规则?答案…...

【门牌制作 / A】
题目 代码 #include <bits/stdc.h> using namespace std; int main() {int cnt 0;for (int i 1; i < 2020; i){string s;s to_string(i);cnt count(s.begin(), s.end(), 2);}cout << cnt; }...

Git+Jenkins 基本使用(Basic Usage of Git+Jenkins)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...
智谱清言:智能语音交互的引领者,解锁高效沟通新体验
哪个编程工具让你的工作效率翻倍? 在日益繁忙的工作环境中,选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度,甚至让团队协作更加顺畅。那么,哪款编程工具让你…...

前端组件库
vant2现在的地址 Vant 2 - Mobile UI Components built on Vue...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...

ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...

yaml读取写入常见错误 (‘cannot represent an object‘, 117)
错误一:yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因,后面把yaml.safe_dump直接替换成yaml.dump,确实能保存,但出现乱码: 放弃yaml.dump,又切…...