pythonのメモリ配置(int型)
突然ですが、pythonのint型変数って不思議じゃないですか??
例えばC言語とかだと、初めに変数(箱)の型を定義して、その中に具体的な値を入れるっていうイメージじゃないですか。
#include <stdio.h>
int main(void)
{
//箱を定義
int x;
//箱の中に数字をいれる
x = 10;
printf("%d\n", x);
printf("%p\n", &x);
return (0);
}
10
0x16f58b678
こんな感じでC言語では、int型変数を定義したタイミングでint型分のメモリ(4byte分)が確保されますよね。
だから、C言語のint型は[-21473648, 2147483647]までの値を取れて、それを超えるとオーバーフローを起こす、と。
#include <stdio.h>
int main(void)
{
int x;
int overflow;
x = 10;
overflow = 2147483648;
printf("%d\n", x);
printf("%p\n", &x);
printf("%d\n", overflow);
printf("%p\n", &overflow);
return (0);
}
$> warning: implicit conversion from 'long' to 'int' changes value from 2147483648 to -2147483648 [-Wconstant-conversion]
overflow = 2147483648;
~ ^~~~~~~~~~
1 warning generated.
10
0x16d4df678
-2147483648
0x16d4df674
こんな感じで。
ここまでは基本となるC言語のint型変数のお話なのですが、pythonはじゃあどうなっているの?っていうお話です。
pythonでは(正確にいうとCPythonでは)、[-5, 256]の整数値はpythonプログラムを実行したタイミングで確保されて、それ以外の数値については、メモリの許す限り必要な分を その都度 確保するという形みたいです。
下の実験例を見てみます。
x = 1
y = 1
print(id(x))
print(id(y))
$>
4297697584
4297697584
ここで、id関数はメモリのアドレス(引数がメモリのどこに配置されているか)を表しているものだと思ってください。
これは、pythonプログラムを動かしたタイミングで、[-5, 256]の整数値がメモリ上に配置されてそこの値を 使い回すという仕様になっています。
さらに、
l = [i for i in range(-5, 257)]
for i in l:
print(f"{i} : {id(i)}")
$>
-5 : 4297697392
-4 : 4297697424
-3 : 4297697456
-2 : 4297697488
-1 : 4297697520
0 : 4297697552
1 : 4297697584
2 : 4297697616
3 : 4297697648
4 : 4297697680
5 : 4297697712
#...(長いので割愛)
241 : 4297893808
242 : 4297893840
243 : 4297893872
244 : 4297893904
245 : 4297893936
246 : 4297893968
247 : 4297894000
248 : 4297894032
249 : 4297894064
250 : 4297894096
251 : 4297894128
252 : 4297894160
253 : 4297894192
254 : 4297894224
255 : 4297894256
256 : 4297894288
これらの数値のアドレスの差分をとると、全て32になっています。このように、pythonプログラムを実行したタイミングで[-5,256]の整数値は32バイト間隔でメモリ上にあらかじめ配置されます。
なので、x = 1とy = 1のどちらもあらかじめ用意された「1」を指し示すのでid関数の結果が同じになるということですね。
また、[-5, 256]以外の整数値については以下の通りです。
x = 999
print(f"x = {x}: {id(x)}")
print(f"x = {x+1}: {id(x+1)}")
x = 1000
y = 1000
print(f"x = {x}: {id(x)}")
print(f"y = {y}: {id(y)}")
x = 999: 4348307024
x = 1000: 4348305936
x = 1000: 4348307696
y = 1000: 4348306576
これを見る限り、1000という数字は必要になった時にメモリをそれぞれ確保されている感じがしますね。(なぜならば、1000という数値を定義するたびにメモリのアドレスが異なるからです)
そしてさらに、pythonではint型の変数として箱のようなものを用意しているわけではないというのもみて取れます。(箱を用意しているのであれば下記のように、中の数値だけ変わるので箱のアドレス自体は変わらない、という挙動になるはず。)
#include <stdio.h>
int main(void)
{
int x;
x = 10;
printf("%d\n", x);
printf("%p\n", &x);
x++;
printf("%d\n", x);
printf("%p\n", &x);
return (0);
}
10
0x16fc735d8
11
0x16fc735d8
//箱の場所は変わらず、箱の中身の数字に1足されるのでアドレスは変わらない
ですが、pythonでは明らかに箱を作るというよりは、必要になった時に1000という数字をメモリ上に配置して、そこへのポインタを変数として定義してる(日本語おかしかったらごめんなさい)という挙動をしてそうです。
図で書くとこんな感じ。
C言語では
pythonでは
なお、[-5, 256]の範囲内の整数値ではこんな感じ。
例として「1」を考えます。
x = 0
x+1 = 1
y = 1として
まとめ
pythonの[-5, 256]の整数値はpythonプログラムを動かしたタイミングでint型オブジェクトがメモリ上に生成・配置され、int型変数はそのアドレスを指し示す。それ以外の数値については必要になったタイミングでint型オブジェクトを生成して、そのアドレスを指し示すということでした。
何か間違い、日本語として不自然なところがあればご指摘お願いします。
ここまでお読みいただきありがとうございました!