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

C语言log10:解析指针与内存管理(上)

解析指针与内存管理(上)

指针是 C 语言中最强大但也最容易让人崩溃的特性。掌握指针,就等于掌握了直接与计算机硬件对话的钥匙。本篇博客将从内存底层的物理机制出发,带你彻底搞懂指针的本质、运算规则以及避坑指南。

1. 内存与地址的物理真相

[cite_start]在计算机中,内存被划分为一个个极小的内存单元 [cite: 1428]。

  • [cite_start]大小:每个内存单元的大小为 1 个字节(Byte),也就是包含 8 个比特位(Bit) [cite: 1428]。
  • [cite_start]地址:每个内存单元都有一个唯一的编号,这个编号就是“地址” [cite: 1429][cite_start]。在 C 语言中,地址就是指针 [cite: 1429]。

地址是如何产生的?
[cite_start]计算机对内存单元的编址并不是靠内部记录实现的,而是通过硬件的地址总线 [cite: 1430][cite_start]。假设在一台 64 位机器上,拥有 64 根地址总线 [cite: 1430][cite_start]。每根线有 0 和 1 两种状态,那么一共就可以表示 $2^{64}$ 种状态(即产生 $2^{64}$ 个地址) [cite: 1430][cite_start]。想要把这么庞大的组合存储下来,恰好需要 8 个 Byte 的空间 [cite: 1430]。

2. 指针变量与平台大小差异

[cite_start]当我们使用 &a 获取变量的地址时,实际上取出的是该数据占用的多个字节中地址最低的那一个 [cite: 1456][cite_start]。为了把这个地址编号存储起来,我们需要使用指针变量 [cite: 1457, 1459]。

[cite_start]极其重要的考点:指针变量的大小与它所指向的数据类型毫无关系,而是完全取决于操作系统平台 [cite: 1487]!

  • [cite_start]在 32 位机器(x86)上,地址数量为 $2^{32}$,指针大小固定为 4 个字节 [cite: 1458]。
  • [cite_start]在 64 位机器(x64)上,指针大小固定为 8 个字节 [cite: 1458]。

3. 既然大小一样,为什么还要区分指针类型?

既然所有的指针在同一台机器上大小都一样,那我们为什么还要费力区分 int*char*double* 呢?区别主要体现在以下两个方面:

  1. [cite_start]解引用的修改权限(步长):指针的类型决定了对它进行解引用时能操作多少个字节的内存 [cite: 1490][cite_start]。例如,int* 类型的指针解引用后可以修改 4 个字节,而 char* 类型的指针只能修改 1 个字节 [cite: 1491]。
  2. [cite_start]指针加减法的跨度:对指针进行 +1 运算时,移动的距离取决于指针类型。对于 int*+1 会向高地址移动 4 个字节;而对于 char*+1 只会向高地址移动 1 个字节 [cite: 1497, 1498]。

补充:万能的 void* 指针
[cite_start]void* 被称为泛型指针,它可以接收任意类型的指针 [cite: 1499, 1500][cite_start]。这种特性常用于函数的参数部分以实现泛型编程 [cite: 1501][cite_start]。但需要注意,void* 指针不能直接进行解引用操作,也不能进行指针的加减运算 [cite: 1500]。

4. 指针的奇妙运算

  • [cite_start]指针 $\pm$ 整数:根据指针类型对应的字节大小,向前或向后移动对应的内存距离 [cite: 1503]。
  • [cite_start]指针 - 指针:两个指针相减,得到的结果是这两个指针之间的元素个数(并非相差的字节数!) [cite: 1521, 1527][cite_start]。可以利用这个绝妙的特性来手写实现 strlen 函数 [cite: 1522, 1527]。
  • [cite_start]指针关系运算:指针之间可以使用 <> 进行大小比较,常用于数组遍历时的边界判断 [cite: 1542, 1548]。

5. const 与指针的排列组合

这是 C 语言考试和面试中最容易让人绕晕的区域。const 的位置决定了到底是谁被锁死了:

  • const int *pint const *p
    [cite_start]修饰的是 *p。指针指向的内容 *p 不可以被修改,但是指针本身的指向 p 可以修改 [cite: 1587, 1609]。
  • int * const p
    [cite_start]修饰的是 p 本身。指针指向的内容 *p 可以被修改,但是指针本身的指向 p 不可修改 [cite: 1610]。
  • const int * const p
    [cite_start]双重锁死,p*p 同时具有上述两种限制,都不可修改 [cite: 1631]。

6. 避开内存刺客:野指针防御指南

[cite_start]野指针是指向了一个不可知的、随机的、或已被回收的内存地址的指针 [cite: 1632]。它会导致程序崩溃或数据被恶意篡改。

三大成因

  1. [cite_start]指针创建后未初始化 [cite: 1634]。
  2. [cite_start]指针进行了越界访问(例如超出了数组的元素个数界限) [cite: 1635]。
  3. [cite_start]指针指向的局部变量空间已随着函数结束被释放 [cite: 1636, 1641]。

防御规范

  • [cite_start]在创建指针但暂时不用时,立刻赋值为 NULL(C 语言中的空指针常量标识符) [cite: 1650]。
  • [cite_start]严格防止指针越界 [cite: 1651]。
  • [cite_start]坚决避免返回局部变量的地址 [cite: 1677]。
  • [cite_start]在使用指针前,一定要通过 if (p) 检查其有效性(是否为 NULL) [cite: 1652, 1667]。
  • [cite_start]使用 <assert.h> 库提供的宏 assert(p) 进行强制断言。如果 p 为假则程序报错停止 [cite: 1678, 1679][cite_start]。在发布 release 版本时,只需在代码最前面加一句 #define NDEBUG 即可一键关闭所有断言检查 [cite: 1697]。

概念拓展:传值调用 vs 传址调用
[cite_start]使用传址调用(传递指针),可以让我们在自定义函数内部,直接跨越作用域去修改主函数中的变量数值(如经典的数值交换函数) [cite: 1698, 1699]。

7. 数组与指针的终极纠葛

[cite_start]在主函数中,数组名称在大多数情况下,直接等同于数组中第一个元素的地址 [cite: 1701, 1702]。
但是,有且仅有两个例外

  1. [cite_start]sizeof(数组名):此时数组名称代表整个数组,计算的是整个数组所占用的字节数总和 [cite: 1703]。
  2. [cite_start]&数组名:对数组名取地址,取出的是整个数组的存储地址(虽然打印出来的值和首元素地址一样,但如果对其进行 +1 运算,它会直接跨越整个数组的长度!) [cite: 1704]。

下标访问的底层本质
[cite_start]在 C 语言中,p[i] 的底层执行逻辑其实就是 *(p + i) [cite: 1731, 1736, 1737][cite_start]。因为加法支持交换律,所以极其变态的是,你甚至可以反过来写成 i[p],程序依然能完美运行 [cite: 1738, 1740]!

数组传参的本质
[cite_start]当我们将数组作为参数传递给函数时,本质上只是传递了一个指针变量 [cite: 1741, 1773][cite_start]。因此在自定义函数内部,如果你写出 sizeof(a) / sizeof(a[0]),在 64 位机器下,sizeof(a) 其实是指针的大小(8 字节),除以 4 字节的 int,返回结果永远是 2 [cite: 1747, 1771, 1773][cite_start]!所以,可以直接在函数的参数列表中使用指针类型来接收数组 [cite: 1774]。


声明:本博客由gemini基于laobie本地obsidian笔记转写,意在将obsidian内置图片转化为了纯文本或表格描述,便于博客上传

http://www.jsqmd.com/news/528055/

相关文章:

  • 聊天总接不上话?这5个技巧让你秒变社交达人!
  • 北京地区老酒回收专业解析|不同需求怎么选?3 家回收行的适配人群精准定位 - 资讯焦点
  • novideo_srgb:实现精准色彩校准的开源工具用户指南
  • 品创共振科技联系方式:关于全网获客服务的客观审视与通用联系指南 - 品牌推荐
  • 漫剧改编的“神兵利器”!唐库新版发布:一键生成500万字+260个深度配角卡 - 资讯焦点
  • 【AI编程系列】Java开发者Cursor AI编程指南:从入门到效率翻倍
  • C语言log9:面向对象思维启蒙:结构体 (Struct) 基础全解
  • YOLO-World实战复现:从环境配置到Demo运行的全流程避坑指南
  • 南方网通讯灵GEO服务重庆代理口碑好的有哪些,费用低吗 - 工业设备
  • C语言_log8:内存越界陷阱与 VS 调试高效技巧
  • log7:函数机制、多文件编程与 static
  • 2026北京茅台老酒回收行业权威排名发布:三大品牌实力领衔,服务口碑双丰收 - 资讯焦点
  • 不用安装LabVIEW也能运行?详解3种LabVIEW程序分发方式的适用场景
  • ChatTTS代码实战:如何通过优化语音合成流程提升3倍处理效率
  • uni-app的生命周期
  • 2026年四川短视频代运营公司推荐:新媒体孵化/视频号/快手孵化/短视频陪跑服务商精选 - 品牌推荐官
  • 如何在普通PC上高效运行macOS:完整实战指南
  • 2026河北PVC彩壳厂家高性价比评测深度解析 - 资讯焦点
  • B站视频缓存转换完整指南:三步解锁m4s格式限制
  • OpenClaw+Qwen3.5-9B多模态实践:截图识别与信息提取自动化
  • 品牌对比:哪些XRF镀层测厚仪综合实力强、用户口碑好? - 品牌推荐大师
  • lessmsi:开源MSI文件提取与分析工具全攻略
  • 如何在Apple Silicon Mac上完美运行iOS游戏:PlayCover终极指南
  • 阿里达摩院AI Earth平台功能调整公告(下线数据检索功能、下线处理与分析功能中的开发者模式、下线模型训练功能和下线应用空间功能等)
  • 紧致眼霜哪个效果好些?2026深度测评抗衰好物排行榜:表层滋养+肌底抗衰 - 资讯焦点
  • 2026农化行业旋盖机优质厂家推荐指南 - 资讯焦点
  • 2026 Java企业AI开发:JBoltAI的实用选型
  • Python测试AI化倒计时:PyPI最新包testgen-ai已突破10万下载量,但93.4%用户仍在用错误配置方式
  • AlienFX Tools:终极Alienware设备控制解决方案,释放硬件全部潜能
  • mybatis增删改查