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

大白话说一说C++指针的非法访问

非AI生成,觉得有用,就请您帮忙点赞转发收藏吧,多谢看官。

目录

非法访问指针(access violation)的危害

非法访问指针的常见行为

1.野指针(wild pointer)

2.悬空指针(dangling pointer)

3.双重释放(double free)

4.越界访问(out-of-bounds access‌)

总结


  • 非法访问指针(access violation)的危害

C++程序运行速度快,指针在这里面绝对是个大功臣,通过指针就能直接操作内存区域的数据,可以实现零开销抽象、零拷贝和高效数据结构。为了追求极致的运行速度,C++几乎舍弃了指针检查,如果每次访问指针都先检查指针是否合法,势必会影响程序的运行速度。C++太信任程序了,默认程序通过指针在访问合法的内存数据,也把指针检查交给了程序,但如果指针使用不当,就相当于给程序埋下了一颗定时炸弹,常见的非法访问指针也可以算是C++编程中的头等”罪行“,操作系统一旦发现这种行为,就算您写了Try-Catch来捕获异常,操作系统也会视而不见,生怕程序再闯出什么大祸,不会给程序改过自新的机会来继续运行,运气差点时,操作系统直接结束程序的生命,也就是软件崩溃或闪退,有时可能运气好点,操作系统没有立即出手,但不会影响程序的最终命运,操作系统终归会在某个时刻给程序判”死刑“并立即执行。

  • 非法访问指针的常见行为

1.野指针(wild pointer)

野指针是代码中只定义但没有指向一块合法可用内存地址的指针,指针p的值不为空,而是一个”垃圾值“,这时使用if(p)来判断指针的合法性是无效的,如果对指针进行解引用操作,就会导致程序崩溃。大白话理解的话,就是小孩出生了,出生证都还没办呢,您就想去上户口,搞这搞那的。

#include <QApplication> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<<"C++野指针测试"; // 只定义了指针,但指针没有指向一块合法和可用的内存区域 // p的值不为空,而是随机指向了一块它没有访问权限的内存区域,p的值是一个”垃圾地址“ int *p; if(p){ // 此处if判断不起作用,程序会对野指针执行解引用操作 // 野指针解引用触发未定义行为(Undefined Behavior, UB),一般都会导致程序立即闪退 *p = 10; } return a.exec(); }

程序运行截图

那如何预防野指针呢?

预防野指针的办法很简单,最简单的办法:定义的时候置空就可以了,动态分配内存后,再重新给它赋值即可。

#include <QApplication> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<<"C++野指针测试"; int *p = nullptr; // 指针置空 if(p){ // 此处if判断不成立 *p = 10;// 此行代码不会执行 } return a.exec(); }
2.悬空指针(dangling pointer)

有些翻译也译作悬垂指针,悬空指针曾经指向了一块合法可用的内存区域,但被程序执行了free或delete操作,导致指针已经没有访问这块内存区域的权限了,但指针的值还是那块内存区域的地址。大白话理解的话,就是曾经有个合法的土地证,但后面那个土地证被注销了,您还想搬出那个旧证,准备在那块地盖房子,那就属于违建了。

#include <QApplication> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<<"C++悬空指针测试"; int *p = new int(0); // 初始化指针,指针指向一块合法和可用的内存区域 if(p){ *p = 10; // 修改内存区域的数据 } qDebug()<<"打印p指向的内存区域的数据:"<<*p; delete p; // 释放p指向的内存区域 if(p){ *p = 20; // 修改内存区域的数据 qDebug()<<"打印p指向的内存区域的数据:"<<*p; } return a.exec(); }

程序运行截图

那如何预防悬空指针呢?

对于C++初学者,如果您不再需要使用这块内存区域,执行free或delete操作后,务必将指针置空,这是一个习惯,也是一条铁律,能帮助我们避免不必要的麻烦。等您学到C++的智能指针,使用智能指针和RAII几乎能避免绝大多数的悬空指针访问。

#include <QApplication> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<<"C++悬空指针测试"; int *p = new int(0); // 初始化指针,指针指向一块合法和可用的内存区域 if(p){ *p = 10; // 修改内存区域的数据 } qDebug()<<"打印p指向的内存区域的数据:"<<*p; delete p; // 释放p指向的内存区域 p = nullptr; if(p){ *p = 20; // 修改内存区域的数据 qDebug()<<"打印p指向的内存区域的数据:"<<*p; } return a.exec(); }
3.双重释放(double free)

如果您对指针执行两次free或delete操作,就属于双重释放,这也是严令禁止的。双重释放也会导致未定义行为,一般也会导致程序直接结束。出现这种问题,比较常见的情况是违背了指针谁创建谁销毁的原则,例如在某个函数正常初始化了一个指针,但可能在多个函数都执行了释放操作。

#include <QApplication> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<<"C++指针双重释放测试"; int *p = new int(0); // 初始化指针,指针指向一块合法和可用的内存区域 if(p){ *p = 10; // 修改内存区域的数据 } qDebug()<<"打印p指向的内存区域的数据:"<<*p; delete p; // 第一次释放p指向的内存区域 delete p;// 第二次释放p指向的内存区域 p = nullptr; if(p){ *p = 20; // 修改内存区域的数据 qDebug()<<"打印p指向的内存区域的数据:"<<*p; } return a.exec(); }

程序运行截图

那如何避免指针的双重释放呢?

初学者记住一个原则:指针谁创建谁释放,在主线程创建的由主线程释放,在子线程创建的由子线程释放,在模块A创建的由模块A释放。不是你负责的活,你不要去抢活干。释放资源后,指针务必置空;释放资源前,务必判断指针是否为空。养成这个好习惯,也能避免绝大部分的指针双重释放问题。

4.越界访问(out-of-bounds access

越界访问是指针访问了不属于它的合法内存范围的数据,也是C++中严令禁止的高危行为。

指针越界访问在 C++ 中属于未定义行为,程序可能崩溃、产生错误结果,也可能看似正常运行,因此必须在编码阶段通过边界检查和工具检测来避免。

常见的越界访问有以下几种:

1.数组越界

int a[5] = {1,2,3,4,5}; int *p = a; p[5] = 10; // ❌ 越界(最大合法索引是 4) *(p + 6) = 20; // ❌ 越界

2.指针偏移越界

int arr[3] = {1,2,3}; int *p = arr + 3; // 指向末尾之后(允许) *p = 10; // ❌ 解引用越界

3.malloc或new之后的越界

int *p = new int[10]; p[10] = 5; // ❌ 越界

那如何避免指针越界访问呢?

避免指针越界访问比避免悬空指针访问要容易很多,关键是做好边界检查和条件判断,不是您能访问的内存区域千万不要随便访问。

有以下几种办法供您参考:

1.能用容器就别用裸指针

std::vector<int> v(10); v.at(i) = x; // 自动检查

2.明确长度,永远带边界判断

if (index >= 0 && index < size) { p[index] = x; }

3.指针遍历时写“死边界”

int *end = arr + len; for (int *p = arr; p < end; ++p) { ... }

4.工具检测(强烈推荐)

工具作用
AddressSanitizer (-fsanitize=address)检测越界、野指针
Valgrind内存错误分析
  • 总结

C++程序中的非法访问指针几乎可以说是C++编程中最致命的问题,也是现代C++之前较难排查的问题,随着现代C++(11/14/17/20)发布,基本可以依靠智能指针、STL容器、RAII规则、程序逻辑检查(Code Review)、单元测试、工具检测来避免非法访问指针的问题。

Bjarne Stroustrup(C++ 之父)曾经说过:”资源管理是编写可靠软件的关键之一“,并将其视为当代 C++ 设计的核心原则。

由于知识水平有限,难免会有错误或不严谨的地方,欢迎批评指正。

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

相关文章:

  • freeRTOS学习
  • 十年,谁来成就你?
  • 带标注的骑电动车是否佩戴头盔数据集,识别率77.1%,1345张图,支持yolo,coco json,voc xml,文末有模型训练代码
  • 如何通过HsMod插件实现炉石传说游戏体验的全面优化
  • 国际化办公首选!全域多语言切换会议录音APP
  • PHP安全编码实战:从SQL注入到XSS攻击的全面防护指南
  • 基于Hermes Agent与Harness Engineering构建生产级AI智能体实战指南
  • 打通智能体的“知识供应链”:OKF 重构 Agent 时代的知识基建
  • 工业视觉质检延迟,核心瓶颈该如何定位?
  • Windows 端 OpenClaw 完整安装流程|全程可视化操作 + 安装包获取
  • GPT-4o真实效能评估:何时该用,何时该弃
  • 锐捷RG-N18000-X 交换机一对多端口镜像(RSPAN)保姆级实战指南
  • 记住窗口位置大小一键恢复免费工具
  • SAM 3 视频分割实战教程:用文本提示分割并跟踪视频中的目标
  • 全驱数字人API实战教程:一张图片即可生成AI数字人(附完整API文档)
  • CAD画图时如何快速地进行图层的设置?-CAD画图基础
  • Triton 编译器在 ROCm 下的应用,自定义 Kernel 开发的桥梁
  • 如何科学评估大语言模型性能:避开虚假版本与误导性跑分
  • ComfyUI v0.27.0更新:Int8模型正式落地,卷积模型加速、Turing显卡支持、视频与多分辨率能力全面增强
  • 【Java毕业设计】中小型汽配企业销售台账管理系统的设计与实现 基于 SpringBoot 的汽车配件供应商与采购销售系统(源码+文档+远程调试,全bao定制等)
  • CTF 基础密码学:模素数二次剩余解题 Writeup
  • 融数筑基联产链·同源贯通兴煤化——孪生空间数据融通 打通煤化工矿生产管理数据链路技术白皮书
  • 让用户选择而不是重新填写
  • 中欧班列物流系统的多线路管理架构
  • 3个核心功能解决你的Windows日志分析困境:为什么LogExpert能成为开发运维的终极利器?
  • STM32学习笔记【30.SPI总线】
  • Excel 的质量管控文档设计
  • zkGolf 竞赛:构建成本最低零知识电路,电路越紧凑得分越优!
  • 如何一键实现8个平台同步直播:OBS多RTMP插件完全指南
  • Python1