回复: id(1) 和 id(2) 返回的内存地址为什么相差 32?

2018-06-18 00:03:22 +08:00
 copie

回复: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 个。 其实我们可以大胆的猜测一下:

看到这些我们就可以解释一下问题。

2709 次点击
所在节点    Python
4 条回复
lance6716
2018-06-18 01:28:20 +08:00
感谢 po 深入挖掘。
另外我还是感觉到新用户水平下滑的很厉害,已经回归平庸大众水平了。看到 po 寻根问底的还是很开心
RLib
2018-06-18 01:40:10 +08:00
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(0)
24
>>> sys.getsizeof(1)
24
>>>
copie
2018-06-18 01:49:13 +08:00
@RLib Python2 小整数的实现原理是 long 和 Python3 是不同的
inframe
2018-06-18 04:14:41 +08:00
py2 里面小整数从-5 到 256 都被缓冲了
看起来和 py3 有些不一样

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/463799

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX