基于python中空指针、野指针陷阱的思考
前言最近在开发一个桌面工具时我用 PySide6 写界面结果遇到一个特别诡异的问题窗口明明关掉了程序却时不时直接崩溃连 Python 堆栈都不给。排查了很久才发现问题出在 Qt 的 C 对象生命周期和 Python 引用不同步上——这让我第一次意识到PySide6 里居然也有类似 C「野指针」的坑。。空指针、野指针在 C空指针(nullptr)指针存0地址指向什么都没有解引用直接崩溃野指针指针存随机垃圾地址内存已经释放但指针还保留这个地址解引用直接崩溃纯Python层面没有传统意义的指针不会出现C那种野指针、空指针崩溃Python全部是引用计数GC没有裸指针。objSomeObject()objNone# 引用清零对象等待GC回收# Python不存在“拿着已经释放的内存地址继续访问”这种野指针# 你最多拿到None访问None的属性只会抛异常 AttributeError不会段错误crashNone近似于 C空指针概念但解引用不会直接程序崩溃抛出Python异常可以捕获Python GC会保证只要还有引用对象就不会被回收引用全部消失才回收内存。不会出现“内存已经释放但变量还指向那块内存”的野指针场景。✅ 纯Python代码不会发生段错误、内存越界、野指针崩溃。重点PySide6 会出现类似「野指针」的严重问题PySide6 是 Python ↔ Qt C 的绑定层。Qt对象本体存活在C堆内存Python侧只是一层包装对象wrapper。场景C端的Qt对象已经被销毁了但是Python这边还保留这个wrapper实例。此时如果调用这个实例的方法就等价于访问已经释放的C对象 → 野指针行为。复现代码典型坑fromPySide6.QtWidgetsimportQWidgetdefcreate_widget():wQWidget()w.setWindowTitle(test)# 没有父对象函数退出后如果C对象销毁returnw objcreate_widget()obj.deleteLater()# 请求Qt在事件循环删除C对象# 过一会C对象销毁但Python obj变量还在# 再调用 obj.show() → 访问已经释放的C内存报错现象有时候直接程序静默崩溃segmentation fault直接退出没有Python堆栈有时候报RuntimeError: Internal C object (PySide6.QtWidgets.QWidget) already deleted.这就是PySide6的“野指针”问题不是Python GC造成是C Qt对象生命周期和Python引用不同步。和GC的关系Python GC回收Python的wrapper包装对象不等于删除底层C Qt对象。反过来Qt C侧把对象销毁Python侧wrapper不会自动置为None。Python GC只管Python堆Qt自己管理C对象生命周期两套内存互不感知。触发C对象提前销毁的常见原因调用.deleteLater()Qt父对象被销毁子对象自动销毁Qt对象树机制最常见parentQWidget()childQWidget(parent)parent.close()# parent销毁 → Qt自动delete子对象child(C内存释放)# 但Python变量child还存在再操作就报already deleted窗口关闭部分对象被Qt自动回收⚠️注意不是Python GC把Qt对象删掉是Qt(C)自己删Python这边还握着包装壳。区分3种情况情况表现是否野指针Python变量 None访问抛AttributeError可捕获❌不是PySide6C对象已销毁Python wrapper还在RuntimeError或者直接进程崩溃✅等价野指针C原生代码裸指针直接段错误crash✅真正野指针PySide6怎么规避这个“类野指针”坑优先利用Qt对象树父子关系管理生命周期不要手动乱用deleteLater()使用前判断C对象是否存活ifobj.isValid():# pyside6 很多对象提供isValid()obj.show()注意不是全部控件都有isValid。通用捕获方式try:obj.show()exceptRuntimeError:# C对象已经销毁跳过操作pass不要保存已经关闭窗口的控件实例用完手动置obj None解除Python侧引用不要跨线程长期持有UI控件实例线程退出后UI对象极易被销毁。补充ctypes / cffi 才会在Python里出现真正野指针如果用ctypes直接操作原生C内存自己调用malloc/freeimportctypes pctypes.pointer(ctypes.c_int(123))ctypes.c_lib.free(p)# p现在就是野指针继续解引用会直接段错误Python直接挂掉只要不直接操作原生C指针普通Python业务不会遇到但PySide6绑定层会遇到上面的Qt生命周期陷阱。总结纯Python、纯GC没有C那种空指针/野指针最多None抛异常。GC不会产生野指针PySide6会出现等价野指针底层C Qt对象销毁Python还持有wrapper包装实例。会直接崩溃或者抛RuntimeError。根源是两套内存管理器Python GC Qt C内存管理不同步不是Python GC的bug。只有ctypes/cffi直接操作C裸指针Python进程才会出现真正的野指针段错误。