回复: id(1) 和 id(2) 返回的内存地址为什么相差 32? - V2EX
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 3275 次点击
    这是一个创建于 2722 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    [问题链接]( 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     Solana     2571 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 08:57 PVG 16:57 LAX 00:57 JFK 03:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86