Python内存地址:Linux上Python 3.5-3.9的不同版本

问题描述

我偶然发现了一个令人困惑的问题。尝试使用ctypes.CDLL("libX11.so")手动关闭Xdisplay来热修复VTK时,我尝试使用Python提供的Xdisplay地址。这在Python 3.8上运行良好,但在Python 3.7上造成了段错误。下面的代码演示了解决VTK中创建的Xdisplay的问题。具体来说:

disp_id = self.ren_win.GetGenericdisplayId()
disp_id_int = int(disp_id[1:].split('_')[0],16)
print(disp_id,disp_id_int)
x11 = ctypes.CDLL("libX11.so")
x11.XClosedisplay(disp_id_int)

运行时返回以下内容

user@host:~/tmp$ python3.7 tmp19.py
0000558f843c5930_p_void 94074887231792
Current thread 0x00007f8145824740 (most recent call first):
  File "/home/alex/python/pyvista/pyvista/plotting/plotting.py",line 2769 in close
  File "/home/alex/python/pyvista/pyvista/plotting/plotting.py",line 4207 in show

user@host:~/tmp$ python3.8 tmp19.py
0000000003568de0_p_void 56004064

如您所见,Python 3.8的地址可以与libX11.so一起使用,但不能与Python 3.7一起使用以关闭Xdisplay窗口。

如注释中所述,从Python返回的内存地址始终相对于当前Python进程。但是,如果是这种情况,为什么某些版本的Python似乎为id(True)返回非常大的地址,而"libX11.so"是在Python初始化时创建的结构。更令人困惑的是,返回这些大值的Python版本在与user@host:~$ python3.5 -c "print(id(True))" 93962585975264 user@host:~$ python3.6 -c "print(id(True))" 10302848 user@host:~$ python3.7 -c "print(id(True))" 94134164615424 user@host:~$ python3.8 -c "print(id(True))" 11498848 user@host:~$ python3.9 -c "print(id(True))" 11374464 库接口时也会引起分段值。

这是Python 3.5-Python 3.9的结果:

SSH_CMD="-e 'ssh -p $PORT'"
rsync $SSH_CMD --chmod=a=rw,Da+x -trv $rsync_target/$target/manifest.csv $pendingdir/$target --delete

SSH_CMD='-e '\''ssh -p 46000'\'''
rsync -e ''\''ssh' -p '46000'\''' --chmod=a=rw,Da+x -trv hexFeed@79.173.143.202::download /ssd/rsync --delete

两个问题:

  • 这是预期的行为吗?如果是这样,我想我在本地安装的版本中已经打开/关闭一个编译器标志。
  • 是否有一种方法可以调整内存地址以获得所有版本的Python一致的结果?

解决方法

您依赖cpython implementation detailid(...)函数返回PyObject的内存地址

在cpython中,Py_TruePy_Falsemacros,pointing at global instances of bool structs

/* Use these macros */
#define Py_False ((PyObject *) &_Py_FalseStruct)
#define Py_True ((PyObject *) &_Py_TrueStruct)

实际的definition of these structs

/* The objects representing bool values False and True */

struct _longobject _Py_FalseStruct = {
    PyVarObject_HEAD_INIT(&PyBool_Type,0)
    { 0 }
};

struct _longobject _Py_TrueStruct = {
    PyVarObject_HEAD_INIT(&PyBool_Type,1)
    { 1 }
};

C全局变量的内存地址由编译器,链接器和动态加载器确定,并且根据常量值,存在未定义的行为

,

事实证明,问题出在我的64位Linux环境上,指针大于4GB,这与使用ctypes包装库时的默认行为不兼容。重复来自@MarkTolonen的评论:

,因为参数类型将在64位系统上错误地传递值。除非另有说明,否则ctypes假定整数参数使用32位整数

技巧是将argtype设置为c_void_p

x11 = ctypes.CDLL("libX11.so")
x11.XCloseDisplay.argtypes = ctypes.c_void_p
x11.XCloseDisplay(disp_id_int)

在Python 3.8上对我有用的原因是指针的地址恰好足够小,可以作为int32传递,而在Python 3.7上,恰好是整数对于int32太大。而是将其作为ctypes.c_void_p来解决。