当前位置: 首页 > news >正文

嵌入式笔记(入门系列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 宏函数 宏函数&#xff0c;宏函数&#xff0c;实际上就是让宏像函数一样被使用。宏函数以函数形式的方式进行入参&#xff0c;但是返回结果是通过表达式求值得到。话说的抽象&#xff0c;我…...

并发编程多线程

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

【十八】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加密&#xff0c;不过是混淆的&#xff0c;但并不影响咱们扣代码 文章仅提供技术交流学习&#xff0c;不可对目标服…...

传知代码-从零开始构建你的第一个神经网络

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

大厂面试真题:SpringBoot的核心注解

其实理解一个注解就行了&#xff20;SpringBootApplication&#xff0c;我们的启动类其实就加了这一个 但是这么答也不行&#xff0c;因为面试官要的答案肯定不止这一个 我们打开SpringBootApplication的源码&#xff0c;会发现上面加了一堆的注解 相对而言比较重要是下面三个…...

Java设计模式—面向对象设计原则(五) ----->迪米特法则(DP) (完整详解,附有代码+案例)

文章目录 3.5 迪米特法则(DP)3.5.1 概述3.5.2 案例 3.5 迪米特法则(DP) 迪米特法则&#xff1a;Demeter Principle&#xff0c;简称DP 3.5.1 概述 只和你的直接朋友交谈&#xff0c;不跟“陌生人”说话&#xff08;Talk only to your immediate friends and not to stranger…...

docker多阶段镜像制作,比如nginx镜像,编译+制作

镜像制作&#xff0c; nginx的源码包 把nginx源码拷贝到容器内 编译要用到gcc make , 以及扩展工具 pcre openssl # "pcre" perl compatibal regulaer expression 刚开始&#xff0c;可以两个终端&#xff0c; 一个手工操作(编译安装、拷贝、环境变量等)&#xf…...

大语言模型量化方法GPTQ、GGUF、AWQ详细原理

大语言模型量化的目的是减少模型的计算资源需求和存储占用&#xff0c;同时尽量保持模型的性能。以下是几种常见的量化方法的原理&#xff1b; 1. GPTQ (Gradient-based Post-training Quantization) GPTQ 是一种基于梯度的后训练量化方法&#xff0c;主要目的是在减少浮点计…...

《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现

摘要 本文深入探讨了 AVL 树&#xff08;自平衡二叉搜索树&#xff09;的概念、特点以及实现细节。我们首先介绍了 AVL 树的基本原理&#xff0c;并详细分析了其四种旋转操作&#xff0c;包括左旋、右旋、左右双旋和右左双旋&#xff0c;阐述了它们在保持树平衡中的重要作用。…...

SAP 特别总账标识[SGL]

1. 特别总账标识(SGL)概述 1.1 定义与目的 特别总账标识&#xff08;Special General Ledger, SGL&#xff09;在SAP系统中用于区分客户或供应商的不同业务类型&#xff0c;以便将特定的业务交易记录到非标准的总账科目中。 定义&#xff1a;SGL是一个用于标记特殊业务类型的…...

认知杂谈77《简单:通往高手的技巧》

内容摘要&#xff1a;          在信息爆炸、关系复杂的时代&#xff0c;简单是复杂背后的真谛。简单如“112”&#xff0c;是智慧的朴素呈现。简单有强大力量&#xff0c;像清泉般纯净&#xff0c;如“我爱你”简单却有力&#xff0c;基础财务知识也体现其在理财中的作…...

《SmartX ELF 虚拟化核心功能集》发布,详解 80+ 功能特性和 6 例金融实践

《SmartX ELF 虚拟化核心功能集》电子书现已发布&#xff01;本书详细介绍了 SmartX ELF 虚拟化及云平台核心功能&#xff0c;包含虚机服务、容器服务、网络服务、存储服务、运维管理、工具服务、数据保护等各个方面。 即刻下载电子书&#xff0c;了解如何利用基于 SmartX ELF …...

9月23日

思维导图 作业 统计家目录下.c文件的个数 #!/bin/bashnum0for file in ~/*.c; doif [ -f "$file" ]; then((num))fi doneecho "家目录下.c文件的个数: $num"...

如何使用Jinja定义dbt宏

dbt宏在dbt框架内的工作方式与传统编程中的函数类似。它允许用户将特定的、通常是重复的SQL逻辑封装到可调用的命名单元中&#xff0c;就像在其他编程语言中用函数来避免重复代码一样&#xff1b;dbt宏定义特定业务的SQL逻辑&#xff0c;然后在dbt项目中需要的地方调用该宏函数…...

深入理解 JavaScript 三大作用域:全局作用域、函数作用域、块级作用域

一. 作用域 对于多数编程语言&#xff0c;最基本的功能就是能够存储变量当中的值、并且允许我们对这个变量的值进行访问和修改。那么有了变量之后&#xff0c;应该把它放在哪里、程序如何找到它们&#xff1f;是否需要提前约定好一套存储变量、访问变量的规则&#xff1f;答案…...

【门牌制作 / 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)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...

智谱清言:智能语音交互的引领者,解锁高效沟通新体验

哪个编程工具让你的工作效率翻倍&#xff1f; 在日益繁忙的工作环境中&#xff0c;选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度&#xff0c;甚至让团队协作更加顺畅。那么&#xff0c;哪款编程工具让你…...

前端组件库

vant2现在的地址 Vant 2 - Mobile UI Components built on Vue...

别再只用JSON了!聊聊Qt里QDataStream的二进制序列化优势与避坑指南

二进制序列化新选择&#xff1a;Qt中QDataStream的高效实践与深度解析 在Qt开发者的工具箱里&#xff0c;JSON和XML常被视为数据交换的默认选择&#xff0c;但当面对高性能、紧凑存储或跨版本兼容性需求时&#xff0c;二进制序列化方案往往能带来意想不到的优势。QDataStream作…...

手把手教你用Verilog在FPGA上实现Sobel边缘检测(附完整Matlab图片转TXT流程)

从图像到硬件加速&#xff1a;FPGA实现Sobel边缘检测全流程实战指南 在计算机视觉领域&#xff0c;边缘检测作为基础预处理步骤&#xff0c;直接影响着后续特征提取和目标识别的精度。传统基于CPU的算法实现往往难以满足实时性要求&#xff0c;而FPGA凭借其并行计算能力和低延迟…...

如何为 OpenClaw 配置 Taotoken 以实现高效的 Agent 工作流

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 如何为 OpenClaw 配置 Taotoken 以实现高效的 Agent 工作流 基础教程类&#xff0c;面向使用 OpenClaw 框架构建 AI Agent 的开发者…...

TLV320AIC3254音频编解码器:核心架构、配置实战与典型应用

1. 项目概述&#xff1a;从一颗“全能”音频芯片说起最近在做一个需要高保真音频采集和处理的嵌入式项目&#xff0c;选型时又一次把目光投向了TI的TLV320AIC3254。这颗芯片在音频工程师的圈子里名气不小&#xff0c;常被戏称为“音频界的瑞士军刀”。它本质上是一颗超低功耗的…...

L298N驱动模块进阶玩法:用Arduino实现直流电机的软启动、缓停与速度曲线控制

L298N驱动模块进阶玩法&#xff1a;用Arduino实现直流电机的软启动、缓停与速度曲线控制 在创客和嵌入式开发领域&#xff0c;直流电机的控制是基础但至关重要的技能。大多数初学者会从简单的正反转和调速开始&#xff0c;但当项目需要更精细的运动控制时&#xff0c;粗暴的启…...

Trillium中文版:破解企业数据治理困局,实现业务驱动数据质量

1. 项目概述&#xff1a;当数据治理遇上“本地化”浪潮最近&#xff0c;业内一个消息引起了我的注意&#xff1a;数据质量与数据集成领域的“老牌劲旅”Syncsort&#xff0c;正式推出了其核心产品Trillium软件系统的中文版。这个消息乍一看&#xff0c;可能只是又一个国际软件厂…...

Tomcat 超精简总结

1. 定位轻量级 Java Web 服务器 / Servlet 容器只跑 Java 项目&#xff08;jsp、servlet、springboot 内嵌&#xff09;处理 动态请求&#xff0c;不擅长静态资源2. 核心作用解析 Servlet、JSP监听端口&#xff0c;接收浏览器请求调用 Java 代码执行业务返回页面 / 数据给客户端…...

从无人机云台到机械臂关节:聊聊FOC力矩控制在机器人里的那些实战坑

从无人机云台到机械臂关节&#xff1a;FOC力矩控制在机器人中的实战精要 当无人机云台在强风中依然保持画面稳定&#xff0c;当机械臂关节能够感知鸡蛋壳的脆弱并精准施力——这些看似简单的动作背后&#xff0c;都离不开一项关键技术&#xff1a;磁场定向控制&#xff08;FOC&…...

Input Leap跨设备键盘鼠标共享3步配置指南

Input Leap跨设备键盘鼠标共享3步配置指南 【免费下载链接】input-leap Open-source KVM software 项目地址: https://gitcode.com/gh_mirrors/in/input-leap Input Leap是一款功能强大的开源KVM软件&#xff0c;能够帮助用户在不同操作系统和设备之间实现键盘鼠标的完美…...

OBS面部追踪插件终极指南:3分钟实现智能直播自动对焦

OBS面部追踪插件终极指南&#xff1a;3分钟实现智能直播自动对焦 【免费下载链接】obs-face-tracker Face tracking plugin for OBS Studio 项目地址: https://gitcode.com/gh_mirrors/ob/obs-face-tracker 在直播和视频录制中&#xff0c;你是否经常需要手动调整摄像头…...