V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
copie
V2EX  ›  Python

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

  •  
  •   copie · 2018-06-18 00:03:22 +08:00 · 2888 次点击
    这是一个创建于 2382 天前的主题,其中的信息可能已经有所发展或是发生改变。

    回复: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 进制)
    4 条回复    2018-06-18 04:14:41 +08:00
    lance6716
        1
    lance6716  
       2018-06-18 01:28:20 +08:00 via Android
    感谢 po 深入挖掘。
    另外我还是感觉到新用户水平下滑的很厉害,已经回归平庸大众水平了。看到 po 寻根问底的还是很开心
    RLib
        2
    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
        3
    copie  
    OP
       2018-06-18 01:49:13 +08:00 via Android
    @RLib Python2 小整数的实现原理是 long 和 Python3 是不同的
    inframe
        4
    inframe  
       2018-06-18 04:14:41 +08:00 via Android
    py2 里面小整数从-5 到 256 都被缓冲了
    看起来和 py3 有些不一样
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1805 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 16:24 · PVG 00:24 · LAX 08:24 · JFK 11:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.