回复:id(1) 和 id(2) 返回的内存地址为什么相差 32 ?
[问题链接]( https://www.v2ex.com/t/463777#reply8 )
由于 V2EX 回复没有 markdown 并且我感觉我的回复有开辟一个主题的资格
int
_PyLong_Init(void)
{
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
int ival, size;
PyLongObject *v = small_ints;
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
size = (ival < 0) ? -1 : ((ival == 0) ? 0 : 1);
if (Py_TYPE(v) == &PyLong_Type) {
/* The element is already initialized, most likely
* the Python interpreter was initialized before.
*/
Py_ssize_t refcnt;
PyObject* op = (PyObject*)v;
refcnt = Py_REFCNT(op) < 0 ? 0 : Py_REFCNT(op);
_Py_NewReference(op);
/* _Py_NewReference sets the ref count to 1 but
* the ref count might be larger. Set the refcnt
* to the original refcnt + 1 */
Py_REFCNT(op) = refcnt + 1;
assert(Py_SIZE(op) == size);
assert(v->ob_digit[0] == (digit)abs(ival));
}
else {
(void)PyObject_INIT(v, &PyLong_Type);
}
Py_SIZE(v) = size;
v->ob_digit[0] = (digit)abs(ival);
printf("这个数字是: %d \n 内存大小是: %d \n 地址是: %p\n",ival,sizeof(PyLongObject),v);
}
PyLongObject *copie = (PyLongObject*) PyLong_FromLong(1<<30);
printf("%d\n",sizeof(copie->ob_digit));
printf("%d %d\n", copie->ob_digit[0],copie->ob_digit[1]);
printf("这个数字是: %d \n 内存大小是: %d \n 地址是: %p\n",1<<30,sizeof(*copie),copie);
我们这里先 hack 一下 Python 源码 hack 部分就是带有 printf 的。
我们再看一下输出
地址是: 0x556ec899ecc0
这个数字是: 253
内存大小是: 32
地址是: 0x556ec899ece0
这个数字是: 254
内存大小是: 32
地址是: 0x556ec899ed00
这个数字是: 255
内存大小是: 32
地址是: 0x556ec899ed20
这个数字是: 256
内存大小是: 32
地址是: 0x556ec899ed40
4
0 1
这个数字是: 1073741824
内存大小是: 32
地址是: 0x7f671b8257b0
Python 3.6.5 (default, Jun 17 2018, 23:20:39)
[GCC 8.1.1 20180531] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
我要解释一下在 Python 源码中小整数是放在一个 static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; 数组中的。
我们看到的是 32 就是 PyLongObject 这个结构体的大小。
然后我们发现一个问题就是我们把创建一个很大的数字,我们看到的这个大小是不会变的。这里就是涉及到 Python3 整 数的存储问题了。
先看一下下面这个 C++ 代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
struct test{
int a;
int b[1];
}test1;
printf("%d\n",sizeof(test1));
test* t = (test*) malloc(sizeof(test1)+sizeof(int)*5);
printf("%d\n",sizeof(t));
}
这里我们 t 的数组 b 的容量肯定比 test 的数组 b 的容量大。
➜ Desktop ./a.out
8
8
我们看到输出都是 8.这就是 Python 使用柔性数组导致的。有兴趣的可以去学习一下柔性数组。
In [1]: import sys
In [2]: sys.getsizeof(0)
Out[2]: 24
In [3]: sys.getsizeof(1)
Out[3]: 28
In [4]: sys.getsizeof(2)
Out[4]: 28
In [5]: sys.getsizeof(1<<30)
Out[5]: 32
我们看到 1<<30 所占的字节数 比 1 占用的多了 4 个,1 比 0 多 4 个。 其实我们可以大胆的猜测一下:
- sys.getsizeof 很有可能获取的是 这个对象在 Python 创建时真实占用的内存数。
- 当创建 0 的时候不占额外的内存。
- 创建 1 等非 0 数的时候都要开辟额外空间。
看到这些我们就可以解释一下问题。
- 在 Python 中一个 PyLongObject(也就是 int) 占用 32 个直接。
- Python 使用柔性数组让我们的 int 类型可以存储无限大的数。
- 通过我们的真实查看 id 求出来的数值 确实是 Python 对象的 C 地址。
- 有兴趣的可以研究一下为什么,我创建 1 << 30 这个数字 而且 v->ob_digit 的值是 10。(提示一下 2 的 30 进制)