Python 内存地址:Python 3.5 - 3.9 在 Linux 之间变化

Python Memory Addresses: Variation Across Python 3.5 - 3.9 on Linux

我偶然发现了一个令人困惑的问题。在尝试使用 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)

其中,当运行、returns:

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 window。

如评论中所述,从 Python 编辑的内存地址 return 始终与当前 Python 进程相关。但是,如果是这种情况,为什么某些版本的 Python 看起来 return 非常大的 id(True) 地址,一个在 Python 初始化时创建的结构。更令人困惑的是,Python 的版本 return 这些大值在与 "libX11.so" 库接口时也会导致分段值。

这是 Python 3.5 - Python 3.9:

的结果
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

两个问题:

你依赖于 cpython implementation detail id(...) 函数 returns 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 的评论:

as the parameter type will pass the value incorrectly on 64-bit systems. ctypes assumes 32-bit integers for integer parameters unless told otherwise

诀窍是将 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 传递解决了问题。