Git常见的命令:

%h 简化哈希
%an 作者名字
%ar 修订日期(距今)
%ad修订日期
%s提交说明

指针简介


指针(Pointer)是C语言的一个重要知识点,其使用灵活、功能强大,是C语言的灵魂。
指针与底层硬件联系紧密,使用指针可操作数据的地址,实现数据的间接访问。

计算机的存储机制一般是以小端进行存储,数组的存储必须是连续的。

指针的加加减减一般用在数组方面。

1. 把内存想对了:地址 + 类型 = 解释方式

  • 地址(address) 是一个无符号整数,指向内存中某个字节的“门牌号”。

  • 类型(type) 告诉编译器“这堆字节该按什么格式解释”,并决定了指针的步长、对齐、* 解引用后得到的对象类型等。

  • 指针(pointer) 就是“携带了类型信息的地址”。

关键观点:指针不是内存本身,指针只是“如何找到并解释内存”的方法。

2. 基础操作:取址、解引用、指针运算

int x = 42;
int *p = &x;       // 取址:&x 的类型是 int*
*p = 100;          // 解引用:写入 x
printf("%d\n", *p); // 读取 xint a[4] = {1,2,3,4};
int *q = a;        // 数组名在表达式中大多数场合衰减为指向首元素的指针,等价于 &a[0]
q++;               // 指针运算:q 跳到下一个 int 元素(步长 sizeof(int))
printf("%d\n", *q); // 打印 2

3. const / volatile / restrict 到底修饰谁?

读法建议:从标识符向右再向左读,先绑定最近的。

const int *p;      // 指向“const int”的指针:不能通过 p 改值,但能让 p 指向别处
int * const p2=&x; // const 指针:p2 自身不可改,但可改它指向的 int 值
const int * const p3=&x; // 都不能改volatile uint32_t *reg;  // 典型寄存器映射:每次读写都不能被优化掉

volatile:告诉编译器不要优化掉对该对象的访问(硬件寄存器/中断共享变量/内存映射IO)。

restrictC99):承诺同一作用域内通过该指针访问的对象不与其他指针别名,利于优化(用于 DSP/大数组运算)。必须百分百保证不别名才用。

4. 函数参数:传入大对象、输出参数、多返回值

传大对象:用指针/const指针避免拷贝。
**输出参数:用指针作为“返回槽位”**实现多返回值。

typedef struct {uint8_t id;uint8_t payload[32];
} Frame;// 仅读入参:用 const 指针,避免拷贝
void process_frame(const Frame *f) {// 不能修改 *f,保证调用方数据安全
}// 多返回值:用指针作为输出槽位
bool parse_header(const uint8_t *buf, size_t n,uint8_t *out_id, uint16_t *out_len)
{if (n < 3) return false;*out_id  = buf[0];                 // 写回调用者的变量*out_len = (uint16_t)buf[1] << 8 | buf[2];return true;
}

5. 返回指针:生命周期必须搞清楚

int* bad(void) {int local = 123;return &local; // ❌ 返回了栈上临时变量的地址,离开函数后悬空
}int* ok_heap(void) {int *p = malloc(sizeof *p); // ✅ 堆上if (p) *p = 123;return p;                   // 记得由调用方 free(p)
}int* ok_static(void) {static int s = 123;         // ✅ 静态存储期(程序结束才回收),但非线程安全return &s;
}

6. 数组与指针的那些“坑”

int a[4];
printf("%zu\n", sizeof(a));      // 16(整块数组大小)int *p = a;
// 注意:在函数形参里写 int a[] 和 int *a 是一样的(数组会衰减为指针)
// 所以在函数里对 a 用 sizeof 得到的是指针大小,而不是数组总大小// 区分“指向数组的指针”与“指针数组”
int (*pa)[4] = &a;   // pa 的类型:指向“含 4 个 int 的数组”
int *ap[4];          // ap 的类型:含 4 个“int*”的数组

字符串常量

char *s  = "abc";   // 旧式写法,"abc" 在只读区,修改 *s 未定义行为
const char *s2 = "abc"; // 推荐:只读
char s3[] = "abc";  // 拷贝到栈上,可以改 s3[0] = 'A';

7. 指针算术、对齐与别名

​​​​​​​

  • p + k 跳过 k * sizeof(*p) 字节。
  • 对齐:把 uint8_t* 强转为 uint32_t* 读写前,要保证地址按 4 字节对齐,否则可能硬件 Fault(很多 MCU 如 ARM Cortex-M 严格对齐)。
  • 别名:通过不同类型指针访问同一内存可能违反 严格别名规则,优化后会出现玄学 Bug。跨类型拷贝请用 memcpy。
// 不要直接用强转跨类型暴力读写
uint32_t get_u32_unaligned(const uint8_t *p) {uint32_t v;memcpy(&v, p, sizeof v);  // 安全、无对齐和别名风险return v;
}

8. 二级指针(指向指针):动态分配/修改实参的指针值

bool make_buffer(uint8_t **out_buf, size_t n) {uint8_t *p = malloc(n);if (!p) return false;memset(p, 0, n);*out_buf = p;   // 修改调用者手里的“指针变量”return true;
}int main(void){uint8_t *buf = NULL;if (make_buffer(&buf, 128)) {// 使用 buffree(buf);}
}

9. 函数指针与回调(状态机/驱动表必备)

// 1) 普通函数指针
typedef int (*cmp_fn)(const void*, const void*);// 2) 嵌入式驱动“虚表”——把不同实现装入同一接口
typedef struct {void (*init)(void);void (*write)(const uint8_t *data, size_t n);size_t (*read)(uint8_t *buf, size_t n);
} UartOps;// 某个具体 UART 的实现
static void usart1_init(void) { /* 硬件初始化 */ }
static void usart1_write(const uint8_t *d, size_t n) { /* 发送 */ }
static size_t usart1_read(uint8_t *b, size_t n) { /* 接收 */ return 0; }static const UartOps USART1_Ops = {.init = usart1_init,.write = usart1_write,.read  = usart1_read,
};// 使用时只依赖接口指针
void app_run(const UartOps *ops) {ops->init();const uint8_t msg[] = "Hello";ops->write(msg, sizeof msg);
}

10. 嵌入式必修:寄存器内存映射(volatile + 结构体)

以 STM32F103 为例(示意,寄存器偏移按参考手册调整):

#include <stdint.h>typedef struct {volatile uint32_t CRL;   // 配置低 8 个 IOvolatile uint32_t CRH;   // 配置高 8 个 IOvolatile uint32_t IDR;   // 输入数据寄存器volatile uint32_t ODR;   // 输出数据寄存器volatile uint32_t BSRR;  // 置位/复位volatile uint32_t BRR;volatile uint32_t LCKR;
} GPIO_TypeDef;#define PERIPH_BASE     (0x40000000UL)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000UL)
#define GPIOA_BASE      (APB2PERIPH_BASE + 0x0800UL)#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)static inline void gpioa_set_pin(uint32_t pin) {GPIOA->BSRR = (1U << pin);   // 写 BSRR 置位
}static inline void gpioa_reset_pin(uint32_t pin) {GPIOA->BRR = (1U << pin);    // 写 BRR 复位
}void blink(void) {// 假设 PA5 已配置为推挽输出gpioa_set_pin(5);// delay...gpioa_reset_pin(5);
}

要点:

  • 所有寄存器字段必须是 volatile,防止编译器优化掉访问。

  • 把基地址强转为“寄存器布局结构体指针”,代码更可读、可维护。

11. 环形缓冲区(UART/传感器常用)——指针版

typedef struct {uint8_t *buf;           // 数据区size_t   cap;           // 容量(必须为 2 的幂以便快速取模,可选)size_t   head;          // 写指针size_t   tail;          // 读指针
} RingBuf;// 初始化:外部提供存储,避免 malloc
void ring_init(RingBuf *rb, uint8_t *storage, size_t cap) {rb->buf  = storage;rb->cap  = cap;rb->head = rb->tail = 0;
}bool ring_put(RingBuf *rb, uint8_t v) {size_t next = (rb->head + 1) % rb->cap;if (next == rb->tail) return false; // 满rb->buf[rb->head] = v;rb->head = next;return true;
}bool ring_get(RingBuf *rb, uint8_t *out) {if (rb->head == rb->tail) return false; // 空*out = rb->buf[rb->tail];rb->tail = (rb->tail + 1) % rb->cap;return true;
}

这里 buf 就是一块“外部管理的内存”的指针,分离存储与逻辑,是指针设计的经典范式。

12. 内存区域与生命周期

  • 栈(auto):函数内局部变量,离开作用域即失效。

  • 静态/全局(static):程序全程有效,初始化一次。

  • 堆(heap)malloc/free 管理,灵活但需负责释放(嵌入式尽量少用或自建内存池)。

13. 常见 Bug 快查表(以及怎么防)

  1. 悬空指针:返回局部变量地址;释放后继续用。→ 规则:谁分配谁释放,置 p=NULL

  2. 越界:指针运算超出对象边界。→ 规则:所有索引做边界检查。

  3. 未对齐访问uint32_t* 指向非 4 字节对齐地址。→ 规则:跨类型用 memcpy

  4. 严格别名违规:不同类型指针访问同一对象。→ 规则:用 memcpy 或通过 unsigned char*

  5. 多线程/中断竞态:ISR 与主循环共享变量未 volatile/未屏蔽中断。→ 规则:volatile + 临界区。

  6. 格式化打印错误printf("%d", p)。→ 规则:指针打印用 %p,强制转换 (void*)p 更稳。

调试建议(PC 端):开启最高警告级别、静态分析、ASan/UBSan(嵌入式可在宿主机先验证算法)。
Keil/MDK:用“Watch/Memory”窗口观察地址,查看 Map 文件确认符号地址;优化等级过高时留意 volatile-O 的交互。

14. 速记清单(工程实践)

  • 读右左法:从变量名向右再向左解析 const/volatile/*/()

  • 函数只读入参:const T *p;输出参:T *out

  • 访问寄存器:volatile 是底线;结构体映射可读性最好。

  • 跨类型读写一律 memcpy;避免未定义行为。

  • 需要修改“指针变量本身”:传 T **

  • 大对象传参用指针,别拷贝;必要时配合 restrict

  • 数组参数在函数里就是指针;sizeof 不再是整块大小。

  • 区分 T (*p)[N](指向数组)与 T *p[N](指针数组)。

  • 回调/状态机/驱动表用“函数指针 + 结构体”。

  • 生命周期先于技术:你指到的东西还活着吗?

15. 小练习

练习 A:swap_int(int *a, int *b);写 split_u16(uint16_t v, uint8_t *hi, uint8_t *lo)
练习 B: 实现一个 hex_dump(const void *buf, size_t n),每 16 字节一行打印地址和内容。
练习 C: 定义 GPIO_TypeDef 并把某个端口第 7 位反转(BSRR/BRR 或 ODR ^= 1<<7)。
练习 D: 用“函数指针表”实现 I2CSPI 的同一 SensorOps 接口,主逻辑只依赖接口。
练习 E: 写一个安全的 read_u32_be(const uint8_t *p)(大端),注意对齐问题。

// A
void swap_int(int *a, int *b){ int t=*a; *a=*b; *b=t; }
void split_u16(uint16_t v, uint8_t *hi, uint8_t *lo){*hi = (uint8_t)(v >> 8); *lo = (uint8_t)(v & 0xFF);
}// B
void hex_dump(const void *buf, size_t n){const unsigned char *p = (const unsigned char*)buf;for(size_t i=0;i<n;i+=16){printf("%p: ", (void*)(p+i));for(size_t j=0;j<16 && i+j<n; ++j) printf("%02X ", p[i+j]);printf("\n");}
}// C(示意)
static inline void gpio_toggle_bit(GPIO_TypeDef *port, uint32_t bit){if (port->ODR & (1U<<bit)) port->BRR  = (1U<<bit);else                       port->BSRR = (1U<<bit);
}// E
uint32_t read_u32_be(const uint8_t *p){uint32_t v;                       // 不直接强转,避免未对齐uint8_t t[4] = {p[0],p[1],p[2],p[3]};// 若目标是小端 MCU(如 Cortex-M),需要字节翻转v = ((uint32_t)t[0]<<24)|((uint32_t)t[1]<<16)|((uint32_t)t[2]<<8)|t[3];return v;
}

16. 下一步进阶建议

  • 指针 + DMA:零拷贝采样缓冲、双缓冲(ping-pong)技巧。

  • container_of/偏移:驱动里常见的“由成员指针反推外层结构体”(注意可移植性与别名规则)。

  • 内存池:在 MCU 上用固定块内存池替代 malloc/free,指针是句柄。

  • 接口抽象:把“操作集合”收拢成结构体的函数指针表,组件可插拔(驱动/协议/UI 控制器都适用)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96096.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96096.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96096.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KMM跨平台叛逃实录:SwiftUI与Compose Multiplatform共享ViewModel的混合开发框架(代码复用率85%)

KMM跨平台叛逃实录&#xff1a;SwiftUI与Compose Multiplatform共享ViewModel的混合开发框架&#xff08;代码复用率85%&#xff09;一、架构革命&#xff1a;跨平台统一状态管理1.1 核心架构设计1.2 技术矩阵对比二、KMM共享ViewModel实现2.1 基础状态管理2.2 ViewModel核心架…

关于Android webview协议混淆

背景&#xff1a;android中引入的html页面是http请求(web服务仅开放了80端口)&#xff0c;但html页面引用的后端接口是https请求&#xff0c;则发生android中html页面请求接口异常<请求无法发送到后端服务(status0)>。浏览器出于安全考虑&#xff0c;要求&#xff1a; 同…

Android Jetpack | Lifecycle

一.前言 本篇主线包含三点&#xff0c;Lifecycle的作用、简单使用、核心原理&#xff08;包含核心类与源码主线分析&#xff09;&#xff1b; 二.作用 官方文档生命周期感知型组件可执行操作来响应另一个组件&#xff08;如 Activity 和 Fragment&#xff09;的生命周期状态…

单片机编程架构

没有最好的程序架构。 只要在项目中实现产品功能并稳定工作&#xff0c;且能在团队内统一应用管理就是最优的程序架构。 一、单片机运行模型&#xff1a; 1.能分配时间的裸机代码 2.FreeRTOS操作系统 代码分层框架&#xff1a; 1.与板关联的底层 2.《驱动底层的驱动层》《中间层…

114. 二叉树展开为链表

题目&#xff1a;给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。…

【Langchain系列三】GraphGPT——LangChain+NebulaGraph+llm构建智能图数据库问答系统

Langchain二次开发专栏 【Langchain系列一】常用大模型的key获取与连接方式 【Langchain系列二】LangChain+Prompt +LLM智能问答入门 【Langchain系列三】GraphGPT——LangChain+NebulaGraph+llm构建智能图数据库问答系统 【Langchain系列四】RAG——基于非结构化数据库的智能问…

【GNSS定位原理及算法杂记6】​​​​​​PPP(精密单点定位)原理,RTK/PPK/PPP区别讨论

PPP 技术详解&#xff1a;原理、流程与 RTK/PPK 对比 在高精度 GNSS 定位技术体系中&#xff0c;除了 RTK 和 PPK 以外&#xff0c;还有一类无需基站即可实现分米到厘米级定位的方法 —— PPP&#xff08;Precise Point Positioning&#xff0c;精密单点定位&#xff09;。它以…

LeetCode 837.新 21 点:动态规划+滑动窗口

【LetMeFly】837.新 21 点&#xff1a;动态规划滑动窗口 力扣题目链接&#xff1a;https://leetcode.cn/problems/new-21-game/ 爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏&#xff0c;描述如下&#xff1a; 爱丽丝以 0 分开始&#xff0c;并在她的得分少于 k 分时…

Codeforces 盒装苹果

题目来源&#xff1a;问题 - 2107B - Codeforces 这道题其实只需要判断两个要点&#xff0c;首先判断一下最大值-1后与最小值的差值是否>k&#xff0c;这里有个小细节&#xff0c;当有多个最大值时&#xff0c;可以先将一个最大值-1后再排序&#xff0c;判断新数组最大值与最…

数据结构--------堆

目录 二叉树 树的概念与结构 非树形结构&#xff1a; 注意&#xff1a; 树的相关术语 树的表示 孩子兄弟表示法 树形结构实际运用场景&#xff08;拓展&#xff09; 1. 文件系统管理 2. 数据库与索引 3. 编程语言与数据结构 信息组织与展示 1. 思维导图 2. 目录与…

VSCode Cursor 大模型 插件扩展 Kilo Code 配置

1.1 概述 Kilo Code 是一个 VSCode Cursor 插件扩展&#xff0c;提供了对多种 AI 模型的支持&#xff0c;包括 Claude Code 和 Qwen3。通过正确配置 Kilo Code&#xff0c;可以在开发过程中获得更好的 AI 辅助编程体验。 Kilo使用文档&#xff1a;https://kilocode.ai/docs/zh-…

深入解析:Unity、Unreal Engine与Godot引擎中的Uniform变量管理

在现代游戏开发中&#xff0c;Uniform变量是实现高质量图形渲染的关键元素。不同游戏引擎对Uniform变量的管理方式有所不同&#xff0c;了解这些差异可以帮助开发者在选择引擎时做出更明智的决策。本文将深入探讨Unity、Unreal Engine和Godot引擎中Uniform变量的管理方式&#…

遨游旅游天地,开启探索未知的梦幻之旅

你是否也怀揣着一颗对世界充满好奇的心&#xff0c;渴望踏上探索旅游世界的奇妙旅程&#xff1f;旅游&#xff0c;是一场与未知的邂逅&#xff0c;是心灵的一次自由翱翔。想象一下&#xff0c;你置身于神秘莫测的撒哈拉沙漠。当夕阳的余晖洒在连绵起伏的沙丘上&#xff0c;那金…

SConscript 脚本入门教程

第一章&#xff1a;什么是 SCons 和 SConscript&#xff1f;核心概念SCons 是一个现代化的构建工具&#xff0c;用于自动化软件构建过程&#xff0c;类似于 Make 但功能更强大、语法更简洁。SConstruct&#xff1a;是 SCons 的主配置文件&#xff0c;通常在项目根目录&#xff…

【深度学习】PyTorch从0到1——手写你的第一个卷积神经网络模型,AI模型开发全过程实战

引言本次准备建立一个卷积神经网络模型&#xff0c;用于区分鸟和飞机&#xff0c;并从CIFAR-10数据集中选出所有鸟和飞机作为本次的数据集。以此为例&#xff0c;介绍一个神经网络模型从数据集准备、数据归一化处理、模型网络函数定义、模型训练、结果验证、模型文件保存&#…

云计算核心技术之容器技术

一、容器技术 1.1、为什么需要容器 在使用虚拟化一段时间后&#xff0c;发现它存在一些问题&#xff1a;不同的用户&#xff0c;有时候只是希望运行各自的一些简单程序&#xff0c;跑一个小进程。为了不相互影响&#xff0c;就要建立虚拟机。如果建虚拟机&#xff0c;显然浪费就…

微信小程序通过uni.chooseLocation打开地图选择位置,相关设置及可能出现的问题

前言 uni.chooseLocation打开地图选择位置&#xff0c;看官方文档介绍的比较简单&#xff0c;但是需要注意的细节不少&#xff0c;如果没有注意可能就无法使用该API或者报错&#xff0c;下面就把详细的配置方法做一下介绍。 一、勾选位置接口 ①在uniapp项目根目录找到manif…

从财务整合到患者管理:德国医疗集团 Asklepios完成 SAP S/4HANA 全链条升级路径

目录 挑战 解决方案 详细信息 Asklepios成立于1985年&#xff0c;目前拥有约170家医疗机构&#xff0c;是德国大型私营诊所运营商。Asklepios是希腊和罗马神话中的医神。 挑战 Asklepios希望进一步扩大其作为数字医疗保健集团的地位。2020年9月&#xff0c;该公司与SNP合作…

高频PCB厂家及工艺能力分析

一、技术领先型厂商&#xff08;适合高复杂度、高可靠性设计&#xff09;这类厂商在高频材料处理、超精密加工和信号完整性控制方面具备深厚积累&#xff0c;尤其适合军工、卫星通信、医疗设备等严苛场景&#xff1a;深南电路&#xff1a;在超高层板和射频PCB领域是行业标杆&am…

AJAX 与 ASP 的融合:技术深度解析与应用

AJAX 与 ASP 的融合:技术深度解析与应用 引言 随着互联网技术的不断发展,AJAX(Asynchronous JavaScript and XML)和ASP(Active Server Pages)技术逐渐成为构建动态网页和应用程序的重要工具。本文将深入探讨AJAX与ASP的融合,分析其原理、应用场景以及在实际开发中的优…