#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题

问题描述

C写python扩展,在执行208行之前拨错。黄色小箭头表示“This is the next statement will be executed”,所以问题出在memcpy这行,然而这是C标准库的函数,不太可能出问题。尝试自己写了一个函数,代替memcpy,报错一样,所以问题不在这里。
#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题

找Bug

但是同一个project中也有其他.c文件使用了memcpy,是没有问题的。

到汇编代码看看,非法访问的地址是怎么产生的。
#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
报错的地方是上面这里,访问寄存器r15所存的地址报错,r15的地址非法。
#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
在寄存器监视器中,可以看到r15的值,0x000000001DF42200,; 在64位机器上,16G内存,编译出的64位代码,作为一个地址,高32位全0是很不正常的。这说明地址的高32位丢失了,导致非法访问。
#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
顺腾摸瓜,我们往上面看一看,movsxd r15, eax, r15的值是由eax从32位有符号扩展而来。
问题来了,eax是32位寄存器,在64位的代码中,为什么会出现32位寄存器呢?并且还做了符号扩展。显然,这是编译器为我们插入的汇编代码。

问题来了,eax的值是啥???
在64位中,rax和eax是同一个寄存器,不过eax仅使用了rax的底32位,这是64位系统为了向下兼容做出的操作。而在32位系统中,只有eax没有rax,因为rax是64位才有的。
在c语言的汇编中,rax/ eax通常保存函数的返回值。

#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题

再往上看一点,发现调用了,PyMem_Malloc函数,这是函数的返回值。
问题又来了,PyMem_Malloc返回32位指针???

PyMem_Malloc是Python源代码中用于分配空间的函数,功能与C标准库的malloc功能类似。
在64位系统中,指针是64位的,因而PyMem_Malloc绝不可能返回32位的指针。而Python已经很多年,所以应该也不是python的问题。

会不会?编译器坏了?
我用的是VS2013,编译器坏了吗?很有可能!不然为什么为我加上了movsxd r15, eax这条符号扩展的机器码????

直到我看到了一篇博客在64位机器上使用返回值为地址的函数要注意声明函数体
!
才恍然发现问题所在!
#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
我在输出框里找到了问题所在!
warining C4013: '‘PyMem_Malloc’ undefined: assuming extern returning int

我没有#include <Python.h>,编译器找不到PyMem_Malloc的函数声明,所以当成extern函数,并假设返回值是int,所以,memcpy需要的是64为指针,所以编译器自动作类型转换,对这个int作了符号扩展,所以添加了movsxd r15, eax.这条语句。

找不到定义不报error,为什么?

#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
首先来了解C编译器的运作原理
对于C的源文件,每一个文件会被编译成中间对象文件(windows中是.obj文件吗, linux是.out文件),然后将中间对象链接起来成为可执行文件(windows是.exe,linux是.out文件)
![在这里插入图片描述](https://img-blog.****img.cn/20190531202222933.png

#掉过的坑#C语言写Python扩展,PyMem_Malloc崩溃的问题
而我的代码结构大概是这样的, 在B.c使用了PyMem_Malloc,但是并没有#include<python.h>。
所以在编译B.c的时候,编译器不知道PyMem_Malloc返回什么,只能假设是int,然后就这样编译了。然后埋下一个坑,就是PyMem_Malloc的定义,会推迟到链接阶段。
正常来说,没有#include这个文件,链接阶段也会找不到PyMem_Malloc的定义,然后报错。然而!在这个project中,A.h包含了对python.h的#include, 居然找到了Python.h的定义,然后把坑填上了!
所以就这样产生了一个可执行文件,还跑了起来,直到跑到memcpy这里,终于访问了坏地址,GG

解决办法

1.在上面的B.c中加入头文件 #include<Python.h>
2在B.c中,将

PyMem_Malloc
PyMem_Free

换成

malloc
free

这两个办法都可以解决这个问题