工作数年还是被指针坑了—谈谈C语言指针运算

C语言零基础入门:从环境搭建到项目实战的完整学习指南 C语言作为计算机编程的基石,其核心在于理解计算机底层逻辑与内存管理机制。通过掌握变量、函数、指针等基础概念,开发者能够深入理解程序在内存中的运行原理,从而编写高效、可控的系统级软件。这种能力对于嵌入式开发、操作系统、编译器等领域具有重要技术价值。在实际应用中,C语言常用于开发硬件驱动、实时系统和性能敏感的应用程序。本指南围绕C语言零基础学习,提供了从开发环境搭建、核心语法学习到项目实战的完整路径,特别针对指针和内存管理等难点进行了详细解析,并强调了调试工具的使用和常见错误排查,帮助初学者避开自学弯路,快速建 阅读详情

C指针:避不开的话题

指针,表征内存地址的一个变量。
与其他编程语言比较,由于指针使用的灵活性和高效性,常常和C的数组/结构体一同使用,算是C语言的一个重要特点了。

一个和指针类型有关的陷阱

笔者在从stack中取数时,发现这样一个问题:
栈底的位置位于0xFFC2_0000,栈顶的位置位于0xFFC0_0000,假设栈底48个字节的数据如下排放:

addressdata
0xFFC2_00000x00
0xFFC1_FFFF0xFF
0xFFC1_FFFE0xFE
0xFFC1_FFFD0xFD
……
0xFFC1_FFF50xF5
0xFFC1_FFF40xF4
……
0xFFC1_FFF00xF0
……
0xFFC1_FFE00xE0
……
0xFFC1_FFD00xD0

指针pBStack指向0xFFC2_0000,如果pBStack为UINT32类型,即无符号4字节整型,那么通过减法运算,永远无法取出以0xFFC1_FFF5为起始地址的32位数据,即用以下code永远无法访问到0xF8F7F6F5的数据:

pBStack = (UINT8*)(UINTN)STACK_BOTTOM;
for(index = 0; index < 48; index++){
  printf("Addr = 0x%x, Data = 0x%x\n", pBStack-index, 
  *((volatile UINT32*)(pBStack-index)));
}

回溯指针的算数运算

指针加上或减去一个整型数据,还是一个指针,问题在于这个指针究竟指向哪个数据单元呢?很显然,下面这些运算结果都是不一样的:
1.(UINT8 *)p + 1
2. (UINT16 *) p + 1
3. (UINT32 *) p + 1
4. (UINT64 *) p + 1
因为当一个指针和一个整数进行加减运算的时候,整数会在执行运算前根据指针的类型进行一定的调整;因此上述1的结果指向下一个字节,2的结果指向+2个字节处,3的结果指向+4个字节处,4的结构指向+8个字节处。
以C语言中常见的数据类型为例,下面列出指针运算的实际指向结果:
指针加法运算结果
还有一种算数运算,即两个指针之间做减法的运算,其结果也是会根据指针的类型进行调整,计算结果并不代表两个地址之间的字节数,而是单元个数。假设存在两个指针p和q,下面这些计算结果也是不一样的:

指针类型p-q的结果
UINT *8p和q之间地址的差值
UINT *16p和q之间地址的差值/2
UINT *32p和q之间地址的差值/4
UINT *64p和q之间地址的差值/8

那么这里更加清楚的看到,指针是指向数据单元的,并不能简单的说其指向一个数据/内容。数据单元的维度是这个指针关键的一个特性。当然还有一个容易理解,容易被初学者接受的方式:假设地址p和地址q分别指向数组里的第x+1个元素和第x个元素,而且p和q与数组的数据类型相同(数组a[]为float型,指针p和指针q也为float型),那么p-q的结果就是中间相隔的元素的个数,结果一定为1!

回溯指针的比较运算

学过汇编的朋友们都知道,比较运算cmp的本质是两个操作数a, b做减法,如果ZF/SF等标志,就可以确定a, b两个数谁大谁小;
因此,可以说指针的比较运算本质上是指针的减法运算,上述p-q的运算也分析过了,下面仅给出一个容易被忽略的指针和数组元素地址比较的case:
假设有数组UINT32 array[],其维度为3,使用UINT32 *pa对其元素进行索引:

#define NUM	3
UINT32 array[NUM];
UINT32 *pa;

那么下面这两种给数组元素赋值0的方式,其结果也是不同的:

//方式1
for(pa = &array[0]; pa < &array[NUM];)
	*pa++ = 0;
//方式2
for(pa = &array[NUM]; pa >= &array[0];)
	*--pa = 0;

各位可以看出方式1和方式2的差异在哪里吗?同样都是数组元素的遍历和赋值,区别在哪里呢?下面就根据执行过程和可能发生的问题做一个描述:
方式1从数组的第一个元素开始,清零,第二个元素,清零,第3个元素也相同,到pa自加第四遍的时候,进行指针的比较运算,会发现pa = &array[3],那么停止清零操作;
方式2从数组的最后一个元素(第3个元素)开始,清零,再第2个元素,第1个元素,到pa自减第4遍的时候,此时的pa已经小于array[0]的地址了,这个时候就有可能发生指针跑飞的现象!
方式2正确的改发是这样,让pa = &array[0]的时候就终止:

//方式2
for(pa = &array[NUM]; pa > &array[0];)
	*--pa = 0;

再看开篇问题

明确了指针的算数运算和比较运算之后,再回过头来看看开篇提到的栈上数据访问的问题。

addressdata
0xFFC2_00000x00
0xFFC1_FFFF0xFF
0xFFC1_FFFE0xFE
0xFFC1_FFFD0xFD
……
0xFFC1_FFF50xF5
0xFFC1_FFF40xF4
……
0xFFC1_FFF00xF0
……
0xFFC1_FFE00xE0
……
0xFFC1_FFD00xD0

pBStack的指针如果是UINT32 *类型,从0xFFC2_0000 stack bottom开始,通过减法只能访问到0xFFC1_FFFC/0xFFC1_FFF8/0xFFC1_FFF4/0xFFC1_FFF0这样地址的数据,因为这里每4个字节组成一个数据单元,每一个指针指向这4个字节的数据单元,可以称之为32Bit对齐。

如果要想访问到0xFFC1_FFF5为起始地址的单元,又不该栈的栈底地址,那么pBStack的类型就得声明为UINT8 *类型;

如果想配合sizeof()对stack中的数据访问,那我们也是统一用UINT8 *类型,就可以轻松避免指针算数运算的陷阱了。

C51嵌入式开发:高效分离浮点数整数与小数部分的原理与实现 在嵌入式系统开发中,浮点数处理是基础且关键的技术环节,尤其在资源受限的8位MCU(如8051)上。其核心原理涉及数据的二进制表示、内存布局及类型转换机制。通过理解浮点数在内存中的IEEE 754格式,开发者可以绕过标准库函数的开销,直接操作数据位或利用强制类型转换的截断特性,实现高效、可控的数值分离。这一技术对于提升系统实时性、减少代码体积具有重要价值,广泛应用于传感器数据处理、人机界面显示、通信协议封装等场景。本文聚焦于C51平台,深入探讨了两种分离浮点数整数与小数部分的经典方法,并详细解析了其中涉及的* 阅读详情

相关推荐

网卡MAC地址硬刷实战指南:芯片识别、固件锁检测与安全刷写

MAC地址硬刷是指直接修改网卡物理芯片(如Realtek RTL8168、Intel I219-V)EEPROM或LAN配置区的底层操作,其核心原理是绕过操作系统,写入永久性硬件标识。该技术具备高持久性与跨系统一致性价值,广泛应用于网络设备测试、嵌入式调试及厂商绑定绕过等场景。但硬刷成败高度依赖芯片型号识别与固件锁状态判断——例如RTL8168需匹配专用Flash工具,而Intel网卡若未解除Flash Descriptor Lock则必然失败。本文聚焦真实可落地的硬刷链路,涵盖PCI设备ID提取、EEPR

weixin_34343308的博客 326

寄存器操作*(volatile uint32 *)含义详解

#define PINMUX3             *( volatile Uint32* )( 0x01C40000+ 0x0C )  PINMUX3 &= ~0x00600000;   分析: 将以上两句合成一条语句相当于 *( volatile Uint32* )( 0x01C40000+ 0x0C )  &= ~0x00600000; 意思是将0x01C40000+ 0x0

持之以恒-先苦后甜 1万+

vscode远程连接deepin出错:过程试图写入的管道不存在的解决方法

背景:昨天我用vscode使用ssh远程开发还处于正常状态,今天刚打开电脑,并启动deepin,再次使用vscode重新连接就出现异常。意思就是22号默认端口连接被拒绝,所以要先确定ssh是否连接。3.重新进入vscode里面,重新连接,如图成功连接。,查看port后面的端口号是不是22号。1.检查sshd是否启动。

qq_43573047的博客 2946

(转)涨姿势,关于类似*(uint32_t*)&GPIOx这样形式的讨论

看了看下面大神的讨论,有些茅厕顿开的感觉,特此搬砖过来 void GPIO_DeInit(GPIO_TypeDef* GPIOx) { /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); switch (*(uint32_t*)&GPIOx) { case GPIOA_BASE...

xuhao0258的博客 2415

机器学习教程 之 半监督学习 Co-training 协同训练 (论文、算法、数据集、代码)

这篇博客介绍的是一篇用于半监督分类问题的方法: 协同训练 Co-training, A. Blum and T. Mitchell, “Combining labeled and unlabeled data with co-training,” in Proc. of the 11th Annual Conf. on Computational Learning Theory, 1998.,这算是半监督领域里面始祖级的论文了,是1998年两位CMU大佬提出的方法。 之前还写过一篇关于Tri-trainin

冯良骏 的 博客 1万+

避坑指南:STM32CubeMX配置USB虚拟串口时Heap Size设置的那些坑

本文深入剖析了使用STM32CubeMX配置USB虚拟串口时,因Heap Size设置不当导致设备无法识别的常见问题。文章从故障现象出发,详细解释了堆内存不足如何影响USB枚举过程,并提供了从内存布局分析、动态监控到系统化调试的完整解决方案,帮助开发者从根本上避免这一配置陷阱。

ttt77的博客 599

c语言中uint32和uint32_t区别

比如 uint32 a; uint32_t ; uint32_t是C/C++ 标准中定义的类型。 //包含以下三个头文件中任意一个,就可以保证使用uint32_t类型 <inttypes.h> <stdint.h> <cstdint> 而uint32是 某些 编译器定义的别名,以方便使用。类似: typedef uint32_t uint32 uint32_t 比 uint32的可移植性更好 。 ...

Hedia Pan的博客 7689

【UE5.3 Cesium for Unreal】GlobePawn编译实战:从源码修改到功能实现

本文详细介绍了在UE5.3中为Cesium for Unreal插件编译和集成非官方增强组件GlobePawn的完整实战流程。从获取源码、准备资产文件、部署项目结构,到逐行修改代码适配工程并解决典型编译错误,最终实现在三维地球上实现类似Google Earth的丝滑鼠标交互体验。

weixin_29011667的博客 307

c语言 枚举类型 uint32_C语言学习篇(26)——枚举enum

引言在日常编写代码或开源项目中,枚举(enum)数据类型到处可见,那么为什么要引入枚举这样的数据类型呢?枚举类型能带来哪些编程时的便利呢?首先枚举和宏定义有一定相似性,那么两者有什么区别呢?什么时候用枚举,什么时候用宏定义呢?今天我们就来聊聊枚举数据类型。概念和一些特性枚举(enum)在C语言中是一些符号常量集。通俗来说:枚举定义了一些符号,这些符号的本质就是int类型的常量,每个符号和一个常量绑...

weixin_39607473的博客 408

避坑指南:英飞凌TC3XX的MCMCAN初始化常见错误及解决方法

本文针对英飞凌TC3XX系列MCU的MCMCAN模块初始化难题,深入剖析了寄存器配置时序、总线监控模式误用、中断映射错误及Message RAM地址冲突四大常见陷阱。通过实战代码示例和诊断流程,提供了清晰的避坑指南和解决方案,帮助嵌入式开发者高效解决CAN FD通信故障,确保汽车电子与工业控制项目的稳定运行。

weixin_29193259的博客 244

linux 基于debian/ubuntu AB系统适配(二)-updateEngine移植

其实A/B系统的原理很简单,大部分的工作都集中在uboot上,rootfs只是起到一个辅助的作用,但这个作用也就必不可少的。你会发现在这个其中并没有kernel的参与。

badbayyj的博客 1033

c语言 枚举类型 uint32_很多程序员都不知道,C语言中还有“快类型”和“小类型”...

虽说C语言是一门很成熟的编程语言,但是近些年来也是有所发展的,从早期的C89到后来的C99、C11等新标准,C语言逐步增加了许多好用的功能,例如新标准头文件“stdint.h”的添加。C语言逐步增加了许多好用的功能stdint.h头文件如果程序员希望写出可移植的C语言程序,首先最重要的一点是不能假定位宽。C语言标准并没有明确指定的 short、int、long 等类型的位宽,因此可能在某些平台 s...

weixin_39924329的博客 418

【OS】AUTOSAR OS系统调用产生Trap的过程详解

AUTOSAR OS系统调用产生Trap的过程详解

qq_36056498的博客 2438

2.3 构建C语言入职教程

2.3.1 职场需求和学校教育的脱节 在工控嵌入式领域,C语言能力是一个几乎要求所有工程师都必须掌握的基础技能,不管你是软件工程师、硬件工程师还是测试工程师。假设你从事嵌入式软件工作,则还会进一步要求你达到相当的深度,工作方可应付自如。 本书侧重于嵌入式软件和架构设计,如果一名新人想在这方面有所建树,嵌入式C语言技能就是必须迈过去的第一道坎。上一节我们找到了快速入门的钥匙,那如何打开“C语言入职”...

zhangmalong的博客 998

uniapp - H5人脸识别认证与活体检测功能(纯前端免费方案+微信sdk人脸识别)完整源码,微信公众号网页/h5端人脸识别功能人脸核身(微信方案提供后端spring boot+thinkphp源码

uniapp实现人脸认证,uniAPP h5手机拍照+人脸识别,免费人脸识别插件,前端人脸识别解决方案,uniapp H5人脸识别带前后摄像头,人脸识别在H5页面进行采集,微信公众号H5人脸核身,H5端人脸识别功能实现,免费h5人脸识别插件方案,微信公众号H5网页实现人脸识别,uniapp人脸监测,uniapp人脸生物监测,使用uniapp开发H5人脸采集功能/人脸识别功能,uniapp项目—h5端人脸采集功能实现,uniapp vue3人脸核身,H5活体检测认证,人脸认证张嘴摇摇头指引动作,人脸核身 微信

🏆 前端领域优质创作者 3808

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

视频教程-C语言编程初级入门-C/C++

C语言编程初级入门 从事编程数年,曾留学日本深造,擅长C++、后端、尤其是逆...

weixin_30705815的博客 359

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

视频教程-C语言提升课程-C/C++

C语言提升课程 从事编程数年,曾留学日本深造,擅长C++、后端、尤其是逆向 ...

weixin_30619363的博客 249

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

C语言用什么写的?自举原理30秒看懂

在编程世界里,C语言一直有着不可替代的地位。无论是操作系统、嵌入式设备、数据库内核,还是如今火热的AI框架底层,都能看到C语言的身影。2026年最新TIOBE编程语言排行榜中

dai_jiaqi的博客 377

《嵌入式Linux与物联网软件开发——C语言内核深度解析》一导读

前言 嵌入式Linux与物联网软件开发——C语言内核深度解析C语言是嵌入式Linux领域的主要开发语言。对于学习嵌入式、单片机、Linux驱动开发等技术来说,C语言是必须要过的一关。C语言学习的特点是入门容易、深入理解难、精通更是难上加难。很多用C语言写了多年单片机程序的老工程师转入嵌入式Linux领域后,都会觉得很难,甚至惊叹“为什么同样是C语...

weixin_34100227的博客 776

C2x将成为C语言的下一个ISO标准

预计将于2022年完成的C2x标准在C委员会的推动下一直在演化。InfoQ采访了Jens Gustedt,他正在委员会内部推进新标准,他还是即将出版的新书“Modern C”的作者。Gustedt最近总结了委员会到目前为止的工作成果,发布了新标准的第一份工作草案。除了当前的C17标准相关工作,其他工作主要针对错误修复,C2x将包含旨在改进语言本身的规范性变更。InfoQ:即将推出的C标准的主要目标...

weixin_33842328的博客 116

C硬核:聊聊预编译指令和宏的应用场景(一)

C语言硬件编程栏目 往期文章:工作数年还是被指针坑了——谈谈C语言指针运算 C硬核:聊聊预编译指令和宏的应用场景C语言硬件编程栏目前言一、预编译指令#define#if#ifdef#include#pragma二、宏的应用场景编译时确定值的参数定义print error level定义函数或函数组合总结 前言 C语言编程中离不开预编译指令,编译器在预编译阶段,会根据预编译指令对你编写的代码进行处理。 例如,我们在写第一段C程序"HelloWorld"的时候,就用到了#include指令来包含stdi

Cerman的博客 717

C硬核:聊聊预编译指令和宏的应用场景(二)

C语言硬件编程栏目 往期文章: 工作数年还是被指针坑了——谈谈C语言指针运算 C硬核:聊聊预编译指令和宏的应用场景(一) C硬核:聊聊预编译指令和宏的应用场景(二)C语言硬件编程栏目前言一、如何方便维护那些值不变的参数?二、如何对你的printf/debug语句分级?1.定义DebugPrintErrorLevel的划分细则2.设计和Error Level相关联的print函数3. 调整宏DebugPrintErrorLevel或入口参数errorlevel三、如何批量替换一系列代码?总结 前言 关

Cerman的博客 836

C++程序设计教程 第三版 钱能著

# 一、 c语言发展历程至今,C语言仍然是世界上最流行且广泛应用的编程语言之一,尤其在系统软件开发、嵌入式系统、设备驱动等领域有着不可替代的地位。二、c语言特点:C语言拥有较少的关键字,仅包含32个左右的关键字,加上简洁的语法结构,使得程序编写起来较为精炼,易于阅读和理解。同时,它的源代码紧凑,减少了不必要的冗余。:C语言允许用户直接访问物理地址和进行位操作,这使得它既能进行底层系统编程,又能进行较高层的应用开发,灵活性极高。:C语言编译生成的目标代码效率接近汇编语言,运行速度快,占用资源相对较少。

m0_67405031的博客 725

C/C++代码缺陷全解析:从内存管理到防御性编程实战

在C/C++开发中,内存管理和资源安全是构建高性能、稳定系统的核心基础。其原理在于通过编译器赋予开发者对内存的直接控制权,这种灵活性带来了性能优势,但也引入了空指针解引用、内存泄漏、缓冲区溢出等常见风险。这些缺陷不仅会导致程序崩溃、数据损坏,还可能被恶意利用形成安全漏洞,严重影响软件质量与安全。在工程实践中,采用RAII(资源获取即初始化)原则、智能指针(如std::unique_ptr、std::shared_ptr)以及现代容器(如std::string、std::vector)是防御此类问题的关键技术

weixin_28683065的博客 245

比Python、Java更快的 Go 语言,能否称霸江湖?

文章来源:jb51.net有一种语言堪称比语言排行榜前五热门选手的Python、Java更快,它就是GO语言。Go于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Ma...

计算机与软件考研 293
上一篇: UEFI是如何处理多事件多任务的
下一篇: 浅谈电力电子中的双脉冲测试
Cerman
博客等级 码龄11年 63粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cerman

你的鼓励是探索和创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值