一覧PG-19-2 · 翻訳処理とメモリの入口

局所変数と参照先の更新

再帰呼出しで局所変数が別々に残る様子を確認し、関数から返したオブジェクトを更新します。続いて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

1
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
2
print("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
3
def make_box(): local = {"items": [10, 20]} return local
4
box = make_box(); alias = box; alias["items"].append(30); print(box, alias is box)
{'items': [10, 20, 30]} True
5
import 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
6
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
7
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
8
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
9
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
10
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

演習完了