Cython中malloc分配3个int空间为何能正常访问第4个元素?
问题原因说明
这是C/Cython这类不带内存边界检查的原生语言的典型特性,本质是你触发了未定义行为,这次运行正常纯粹是巧合,不代表代码写对了。
首先明确:malloc(3*sizeof(int)) 确实只给你分配了合法持有、可安全访问的3个int长度的内存,对应合法访问范围就是qux[0]、qux[1]、qux[2]三个位置,访问qux[3]属于标准的堆越界,本身就是非法操作。你这次能正常读出7,具体原因有三个:
- 原生指针根本没有边界校验:C/Cython里的指针本质就是个存内存地址的整数值,做偏移读写的时候,不管你偏移到哪,只要目标地址是进程有读写权限的内存,CPU就会老老实实执行读写操作,不会主动判断这个地址是不是你之前malloc申请的范围,自然不会主动报错。
- 内存分配器不会严格卡着申请长度划内存:主流系统的堆分配器(比如Linux glibc的ptmalloc)返回的内存块实际大小都会比你申请的大一点——一方面要做内存地址对齐(通常按16字节对齐),另一方面每个块还要留空间存分配元数据。你申请3个int一般是12字节,分配器实际给你的块大概率是16字节,刚好能放下第4个int也就是
qux[3],这个位置属于分配块里没承诺给你的冗余空间,暂时读写不会触发段错误。 - 测试场景太简单没有干扰:你申请完内存立刻就写
qux[3],写完马上就读,这时候这个内存位置还没被其他代码申请占用,你写进去的7没被别的数据覆盖,自然能原样读出来。
千万别觉得这次跑通了就说明越界访问没问题:未定义行为的结果是完全不可控的——换个系统、换个编译器优化等级、甚至代码里多写两行别的内存操作,这个位置就可能被别的变量占用,到时候要么读出来垃圾值,要么程序直接崩溃,甚至可能悄悄篡改别的数据,引出藏几个月都查不出来的诡异bug。malloc申请的内存大小当然决定了你的合法访问边界,越界访问不管跑多少次看起来正常,都是错的。
你运行的测试代码如下:
%load_ext cython %%cython -a from libc.stdlib cimport malloc def main(): cdef int *qux = <int*>malloc(3*sizeof(int)) qux[0] = 4 qux[1] = 5 qux[2] = 6 qux[3] = 7 print(qux[0]) print(qux[3])
额外提一句,实际写这类代码的时候,malloc返回的指针用完一定要记得调用free释放,不然还会有内存泄漏问题。
内容的提问来源于stack exchange,提问作者ignoring_gravity
相关产品推荐
相关产品推荐

