書いて覚えるSwift入門

第48回 メモリ管理

この記事を読むのに必要な時間:およそ 3 分

値の値段

前回取り上げたSwiftに対する不満の1つとして,メモリ管理が言語と密結合していることを取り上げました。

密結合しているおかげで循環参照の解消などをプログラマが制御できるのですが,密結合しているということは別の手法を導入したり,メモリ管理そのものをSwiftで実装することが難しいということでもあります。

今回はメモリ管理とはいったい何なのか,いつどんなときにそれが必要なのかを確認したうえで,それを通してSwiftのよさを見直します。

Swiftの型,CPUの型

Swiftには多彩なデータ型が標準装備されているうえ,それらを組み合わせて新たな型を定義するのも容易です。しかし一度ネイティブコード(nativecode)にコンパイルされてしまえば,そこに残る型はわずか4種類。

UInt8UInt16UInt32そしてUInt64。符号付き整数?浮動小数点数?……それらは演算のときだけ,そうキャスト(cast)されているに過ぎず,メモリ上ではただの符号なし整数なのですから。そしてメモリは64bitアーキテクチャであれば264=18,446,744,073,709,551,616個のInt8からなるただの配列です。少なくとも,ユーザープログラム――の実行単位であるスレッド(thread)の視点からは。

図1は,広大な領域に見えますが,しかしその領域のほとんどは非実在。非実在領域に無理にアクセスしようとしたプログラムはOSに殺されます。これがSegmentation Fault =SEGVです。無尽蔵に見えても実は貴重な不動産を,プログラムはどう活用しているのでしょうか?

図1 メモリ領域

図1 メモリ領域

スタック(stack)と函数(function)

何百冊に及ぶ長編作品(story)もその大部分はTwitterのつぶやき1つに収まる程度の文(sentence)から成っているように,現代のプログラム(program)も数多のサブルーチン(subroutine)から成っています。Swiftを含めむしろ函数(function)と呼ばれるこの実行単位ですが,プログラムの実行は函数が函数を呼ぶ連鎖で成り立っています。だとしたら函数ごとに必要な領域をとなりに確保して,終わったら元に戻すというのは立派なメモリ管理法と言えるでしょう。

図2が,スタック(stack)。デバッガ(debugger)でよく見かけるアレです。図2では階乗(factorial)をデバッガで追っていますが,左下のスタックトレースに同じ名前の函数が並んでいることからもわかるとおり,スタックを使うことで,再帰的(recursive)に実装された函数も難なく扱えます。管理に必要なデータは2つだけ。現在実行中の函数のスタックの上限と下限。ポインタ(という名のUInt642つ。それももちろんスタックに乗せておけばいい。

図2 Stacktrace

図2 Stacktrace

こんまりメソッド注1に引けを取らないぐらいときめきますね。実際,こんまりメソッド以前にもっとも有名になった超整理法の正体もスタックです。バイト列の代わりに書類を,メモリの代わりに本棚をそれぞれ用いただけ のものです。

ではスタックさえあればどんなプログラムも動かせるのでしょうか? 理論上はYesです。しかしスタックには大の苦手が1つあります。それは可変長のデータ。たとえば画像をロードしたいとして,1×1ピクセルのPNGであれば95bytesにおさまりますが,iMac Retina 5kのスクリーンショットは圧縮なしで60MB近くあります。スタックは通常1スレッドあたり8MBしかなく,拡張しても64MBしかありません。

しかし起こり得る最大のメモリ使用量をあらかじめ確保しないともっとヤバイことになります。そう。バッファオーバーフロー(buffer overflow⁠⁠。それがいかに簡単に起こり得るかは,次のCプログラムを試してみればわかります。

Cプログラムのサンプル

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char s0[8] = "ABCDEFG";
    char s1[8] = "0123456";
    printf("s0@%p was ・"%s・"・n", s0, s0);
    printf("s1@%p was ・"%s・"・n", s1, s1);
    char *p = s1;
    strncat(p, "789abcde", 15);
    printf("s0@%p is ・"%s・"・n", s0, s0);
    printf("s1@%p is ・"%s・"・n", s1, s1);
    return 0;
}

シェルでの実行

% cc -Wall bufof.c && ./a.out
s0@0x7ffee8b96990 was "ABCDEFG"
s1@0x7ffee8b96988 was "0123456"
s0@0x7ffee8b96990 is "89abcde"
s1@0x7ffee8b96988 is "0123456789abcde"

それでは,このような事前に必要量が見積もれないデータはどこにおけばいいでしょう?

注1)
人生がときめく片づけの魔法 改訂版

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。