局所変数と参照先の更新
再帰呼出しで局所変数が別々に残る様子を確認し、関数から返したオブジェクトを更新します。続いてctypesの32ビット整数配列を使い、ポインタの参照先を変える操作と、参照先の値を書き換える操作を比較します。
説明
関数の呼出しごとに、引数や局所変数を保持する実行フレームができます。一方、その関数で作ったオブジェクトは、戻った後も別の場所から参照されていれば利用できます。
再帰呼出しで局所変数が別々に残る様子を確認し、関数から返したオブジェクトを更新します。続いてctypesの32ビット整数配列を使い、ポインタの参照先を変える操作と、参照先の値を書き換える操作を比較します。
1呼出しごとの値を表示する関数
nを一つずつ減らして呼び出し、各呼出しの局所変数localを表示します。
def descend(n):
local = n * 10この呼出しの局所値
print("enter", n, local)
if n == 0:
print("return", n, local)
return local
child = descend(n - 1)別のフレームで再帰呼出し
print("resume", n, local, "child", child)親の値と子の戻り値
return local + childこの呼出しの戻り値localは各呼出しで作られます。子の呼出しから戻ったときに、親のnとlocalが保持されているかをresumeの表示で確認します。
2内側から戻るときの局所変数
n=2から実行します。
print("result", descend(2)三つの呼出しを作る)2・20、1・10、0・0の順に入り、0から戻ると親の1・10、さらに2・20へ戻ります。子がlocal=0を使っても、親のlocalは上書きされません。ここで扱うフレームは実行上の呼出し情報であり、全ての変数がCPUのスタック領域に直接置かれるという意味ではありません。
3関数で作ったオブジェクトを返す
関数内で辞書とリストを作り、辞書を返します。
def make_box():
local = {"items": [10, 20]}関数内でオブジェクトを作る
return local参照を呼出し側へ返すlocalという局所名が呼出し終了後に使えなくなっても、返されたオブジェクトを呼出し側が保持できます。オブジェクトの寿命と、局所名を利用できる範囲は同じではありません。
4戻った後の共有参照
戻り値を二つの名前で参照し、一方から更新します。
box = make_box()戻り値を保持; alias = box同じ対象への別名; alias["items"].append(30)参照先のリストを更新; print(box, alias is box同じ辞書か確認)boxからも30が見え、同じ辞書を指すことがTrueで確認できます。関数がreturnしただけで、この辞書やリストが消えたわけではありません。
532ビット整数の配列を用意する
ctypesでC互換の符号なし32ビット整数を二つ並べます。実際のバイト順と保存バイト列を表示します。
import ctypes as c, sys; words = (c.c_uint32 * 2)(0x11223344, 0x55667788)32ビット値を二つ確保; base = c.addressof(words)配列先頭のアドレス; print(sys.byteorder, c.sizeof(c.c_uint32), c.sizeof(words)型と配列のバイト数); print(bytes(words).hex(" ")実際の保存バイト列)この環境はlittle-endianで、一要素4バイト、配列全体8バイトです。0x11223344は44 33 22 11の順で保存されています。以後はこの配列内だけを参照します。
6同じ先頭を指すポインタ
配列先頭を指すポインタpを作り、qにも同じポインタを参照させます。アドレスは配列先頭からの差で表示します。
p = c.cast(words, c.POINTER(c.c_uint32))32ビット値へのポインタ; q = p同じポインタへの別名; print(c.addressof(p.contents) - base参照先の位置, hex(p[0]), hex(p[1])連続する二要素を読む)先頭からの差は0で、p[0]は最初の値、p[1]は4バイト先の次の値です。添字1は1バイト先ではなく、ポインタの要素型一つ分だけ先を示します。
7ポインタ経由で値を書き換える
p[0]へ新しい32ビット値を書き込みます。
p[0] = 0xAABBCCDD参照先の32ビット値を更新; print([hex(n) for n in words]配列の値); print(bytes(words).hex(" ")更新後のバイト列); print(hex(q[0])別名から同じ場所を読む)配列の最初の値と最初の4バイトが変わり、qからも同じ値が見えます。pという名前を別のポインタに変えたのではなく、参照先の保存領域を書き換えました。
8ポインタの参照先を変更する
pを配列の二番目を指す新しいポインタへ付け替えます。
p = c.cast(c.byref(words, 4)先頭から4バイト先, c.POINTER(c.c_uint32)参照する要素型); print(c.addressof(p.contents) - base, c.addressof(q.contents) - base二つの参照先); print(hex(p[0]), hex(q[0])各参照先の値)pは先頭+4、qは先頭+0を指します。新しいp[0]は配列の二番目で、q[0]は最初の値です。名前pの付替えは、qが指すポインタまで変更しません。
9異なる参照先へ書き込む
pとqを通して二つの領域へ別々に書き込みます。
p[0] = 7二番目の要素へ書く; q[0] = 0x01020304最初の要素へ書く; print([hex(n) for n in words]); print(bytes(words).hex(" ")二つの値の保存状態)最初の値は0x01020304、二番目は7です。どちらも添字0ですが、参照元のポインタが指す場所が異なります。
10同じ保存領域をバイト単位で更新する
同じ配列へ8ビット値としてアクセスし、最初の1バイトだけを変更します。
octets = c.cast(words, c.POINTER(c.c_uint8)1バイト幅で参照); octets[0] = 0xFF先頭の1バイトだけ更新; print(bytes(words).hex(" ")); print(hex(words[0]), hex(words[1])32ビット値として読み直す)先頭の04だけがffになり、32ビット値としては0x010203ffになります。型は参照する幅と解釈へ影響します。範囲外や寿命が切れた領域を参照してよいという意味ではなく、ここでは保持している8バイト内の操作だけを行っています。
まとめ
呼出しごとのフレームは、それぞれの引数と局所変数を保持します。
関数が戻っても、呼出し側から参照されるオブジェクトは利用できます。
ポインタの付替えと参照先の更新は別の操作です。要素型の幅、バイト順、有効な範囲を対応させて読み書きします。
操作履歴と解説を振り返れます。
Python 3
def descend(n):
local = n * 10
print("enter", n, local)
if n == 0:
print("return", n, local)
return local
child = descend(n - 1)
print("resume", n, local, "child", child)
return local + childprint("result", descend(2))enter 2 20 enter 1 10 enter 0 0 return 0 0 resume 1 10 child 0 resume 2 20 child 10 result 30
def make_box():
local = {"items": [10, 20]}
return localbox = make_box(); alias = box; alias["items"].append(30); print(box, alias is box){'items': [10, 20, 30]} Trueimport ctypes as c, sys; words = (c.c_uint32 * 2)(0x11223344, 0x55667788); base = c.addressof(words); print(sys.byteorder, c.sizeof(c.c_uint32), c.sizeof(words)); print(bytes(words).hex(" "))little 4 8 44 33 22 11 88 77 66 55
p = c.cast(words, c.POINTER(c.c_uint32)); q = p; print(c.addressof(p.contents) - base, hex(p[0]), hex(p[1]))0 0x11223344 0x55667788
p[0] = 0xAABBCCDD; print([hex(n) for n in words]); print(bytes(words).hex(" ")); print(hex(q[0]))['0xaabbccdd', '0x55667788'] dd cc bb aa 88 77 66 55 0xaabbccdd
p = c.cast(c.byref(words, 4), c.POINTER(c.c_uint32)); print(c.addressof(p.contents) - base, c.addressof(q.contents) - base); print(hex(p[0]), hex(q[0]))4 0 0x55667788 0xaabbccdd
p[0] = 7; q[0] = 0x01020304; print([hex(n) for n in words]); print(bytes(words).hex(" "))['0x1020304', '0x7'] 04 03 02 01 07 00 00 00
octets = c.cast(words, c.POINTER(c.c_uint8)); octets[0] = 0xFF; print(bytes(words).hex(" ")); print(hex(words[0]), hex(words[1]))ff 03 02 01 07 00 00 00 0x10203ff 0x7
演習完了