HN 日本語サマリー

← 一覧へ戻る
プログラミング

失われたmalloc()

The lost malloc() (napcakes.nekoweb.org)

136 pointsby matt_d53 コメント

要約

この記事は、Unixの初期バージョンにおけるメモリ割り当て(malloc)の進化をたどります。初期の線形アロケータから、より構造化された`dmr/malloc`、そして現代的な`malloc`/`free`インターフェースに至るまでの変遷を、ソースコードを引用しながら解説しています。また、C++の`new`演算子の起源や、初期のプログラマがメモリ管理にどのようにアプローチしていたかについても考察しています。

全文翻訳

The lost malloc()

2026-10-29

(Sorry, I couldn't resist not putting a catchy title :)

別のプロジェクトのアイデアに触発され、Unixの考古学的な調査を行い、オリジナルのソースを調べました。他人のコードを読むのは、歴史的な重要性からか、これまでにないほど楽しかったです。また、現代の巨大なコードと比較して、驚くほど分かりやすかったことも理由の一つです。

この記事は、C言語におけるメモリの割り当てと管理という、地味で退屈なタスクがどのように処理されていたかについて、短く(あるいはそれなりに)掘り下げます。将来的には、Unix OS、その設計、アーキテクチャ、実装についてさらに深く読みたいと思っています。

遠い昔...

遠い遠いPDPで、私たちの旅は、50年以上前の1973年11月にリリースされたUnixの第4版から始まります。C言語は実際にはその前年にリリースされた第2版で初めて登場しましたが、広くは使われていませんでした。V4が特別なのは、カーネルの大部分でさえ、その時点では(「中世の」とでも呼びましょうか)C言語で書かれていたからです。システムは成長し、非常に有能になっていました。今日私たちがPOSIX環境で目にしている多くのUnixの機能が、すでにV4に存在していたことがわかります。

超豆知識: V4を含むテープが発見されたのは昨年です!それ以前は、完全に失われたと考えられていました。

[画像: 1973年頃のベル研究所のUNIX v4の磁気テープ]

倉庫を整理中に、スタッフが1973年頃のベル研究所のUNIX v4を含むこのテープを発見しました

~ Rob Ricci

線形割り当て

最初の興味深い点は、C言語の「コンパイラ」ソースコードの中に見られます。「コンパイラ」と引用符で囲んだのは、今日私たちが知っているようなモノリシックなコンパイラではなかったからです。むしろ、Cコードからオブジェクトファイルを得るために一連の個別のツールをチェーンでつなぐものでした。

/usr/c/c20.c:814には、私が理解する限りでは非常に原始的なオプティマイザ(V5では「Cオブジェクトコード改良ツール」と呼ばれていますが、V4にはコメントが少ないです)があり、次のようなコードが見られます。

alloc(an)
{
    register int n;
    register char *p;

    n = an;
    n++;
    n = & ~01;
    if (lasta + n >= lastr) {
        if (sbrk(2000) == -1) {
            write(2, "Out of space\n", 14);
            exit(1);
        }
        lastr =+ 2000;
    }
    p = lasta;
    lasta =+ n;
    return(p);
}

これは魅力的ではありませんか?このルーチンは、プログラム全体でメモリ割り当てに使用されています。そして、目を細めて見れば、これは単なる線形アロケータです。サイズをアラインし、領域が十分かどうかを確認し、そうでなければ拡張を試みます。失敗した場合は、単に冷たく終了します。これは、呼び出し側が毎回NULLをチェックする必要がないことを意味し、実際、彼らはそうしていません。

「領域」には名前がありません。今日のようにアリーナを使用するのとは異なります。しかし、上記のコードは、sbrk()を使用することでヒープを拡張する際にポインタが無効にならないという、良い特性を持っています。

そして、メモリを解放することに手間をかけていません。なぜなら、必要がないからです。プログラムの実行ライフタイム中はすべてが存在します。

dmr/malloc

これは私の意見では最も興味深い部分であり、私がこのタイトルを選んだ理由です。

Dennisのディレクトリには、malloc.cという名前のファイルがあります。そして、そこに2つの関数が見られます。

struct map {
    char *m_size;
    char *m_addr;
};

malloc(mp, size)
struct map *mp;

mfree(mp, size, aa)
struct map *mp;

見覚えがありますか?はい、mfree()にmが付いたものです。これらの関数は明らかにメモリの(非)割り当てを扱っており、私たちが今日慣れ親しんでいるものに先行するものです。しかし、インターフェースは異なります。

malloc()は、mp(「マップ」用)と呼ばれる追加の最初の引数を受け取り、mfree()も同様です。それは何でしょうか?さて、それらがどのように使用されているか見てみましょう。

p = malloc(coremap, sizeof foo);
...
mfree(coremap, sizeof foo, p);

q = malloc(swapmap, sizeof bar);
...
mfree(swapmap, sizeof bar, q);

つまり、coremapやswapmapといったオブジェクトを渡しています。それらは何でしょうか?

/usr/sys/systm.h:2から:

int	coremap[CMAPSIZ];
int	swapmap[SMAPSIZ];

それらは単なるフラットなメモリ領域です!したがって、これらのmalloc()とmfree()は明示的なメモリ領域で動作します!それだけでなく、mfree()は解放するオブジェクトのサイズを、本来そうすべきであるように受け取ります。それらは、従来のmalloc()/free()よりも無限に優れたインターフェースです。

このようなインターフェースを持つライブラリの使用を想像してみてください。そのライブラリに委任した領域全体を破棄するだけで、すべてのメモリリソースを解放できます。ヒープ破損バグは、メモリをパーティション化できるため、より深刻でなくなるでしょう。サードパーティのコードが割り当てるメモリ量を簡単に制限できます。リストは延々と続きます...

(補足:ここでは少しミスがあり、領域はそれぞれCMAPSIZ/2とSMAPSIZ/2のサイズであるべきでした。後で修正されました。)

しかし、これらの利点のすべてがDennisとKenの念頭にあったとは思いません。では、なぜ彼らはそのインターフェースを採用し、メモリを「コア」と「スワップ」に分割したのでしょうか?その理由は、ハードウェアの制約だけだったと私は信じています。これらのインターフェースは、1975年春にリリースされたV6(おそらくV7も?)までこのまま維持され、幸いなことに、そのエディションに関する悪名高いほど優れたリソースがあります。

[画像: Lions's Commentaryのコピーを持つBrian Kernighan]

Brian Kernighan、Wikipediaより。

John Lions著『A Commentary on the Sixth Edition UNIX Operating System』より:

これらは、2種類のメモリリソース、すなわち:

  • 32ワード(64バイト)単位のメインメモリ;
  • 256ワード(512バイト)単位のディスクスワップ領域。

これらの各リソースについて、利用可能な領域のリストがリソース「マップ」(「coremap」または「swapmap」)内に維持されます。「malloc」および「mfree」には常に適切なリソース「マップ」へのポインタが渡されるため、ルーチン自体は、どの種類のリソースを扱っているかを知る必要がありません。

そして、基盤となる割り当てアルゴリズムがどのように機能するかを説明しています。これらは線形割り当てを行いません(したがってmfree()があります)。基本的に、アドレスでソートされたブロックのリスト(struct map)であり、ファーストフィットを行い、隣接するブロックを結合します。

第6版は、「arena」という用語が登場する最初の版でもあります。/usr/source/s4/alloc.s:17にあります。

/char *allocs[2] { /*initial empty arena*/
巻き戻し

...線形割り当ての話題に戻って... V6の/usr/source/s1/diff1.c:88にある、おそらく史上初のdiff(1)のバージョンには、次のようなコードがあります。

char *area;
char *top;
alloc(n)
{
    register char *p;
    p = area;
    n = (n + 1) & ~1;
    area =+ n;
    while (area > top) {
        if (sbrk(1024) == -1) {
            mesg("Out of space\n");
            exit(1);
        }
        top =+ 1024;
    }
    return(p);
}

これは以前のalloc()とそれほど違いはありませんが、今回は任意の割り当てサイズを処理します。whileループを使用して領域を拡張し続け、十分なスペースが確保されるまで停止します。実際の興味深い部分は後で、unsort():143にあります。

unsort(f, l, b)
struct line *f;
int *b;
{
    int *a;
    int i;
    a = alloc((l + 1) * sizeof(a[0]));
    for (i = 1; i <= l; i++)
        a[f[i].serial] = f[i].value;
    for (i = 1; i <= l; i++)
        b[i] = a[i];
    area = a;
}

この関数が正確に何をするのかはよくわかりませんが、重要ではありません。重要なのは、メモリ(a = alloc(...);)を割り当て、その後領域を巻き戻している(area = a;)ことです!その後、bにコピーしているため、これは冗長だったようです。

サイズの計算

ほとんどのコードはmalloc()/alloc()とその仲間を直接呼び出し、サイズ計算ロジックをインライン化しています。先ほど見た行のように:

a = alloc((l + 1) * sizeof(a[0]));

私はもうそのようなことはしませんし、あなたもすべきではありません!私はC++のnewに似たいくつかのマクロを使用しており、すべてのsizeof処理を扱います。しかし、私は愚か者です。きっとこれらの人々も同じような問題に遭遇したはずです。もしそうなら、彼らはどうしたのでしょうか?V7にジャンプしてみましょう。1979年1月リリース、V6の4年後です。このマクロが見つかります。

#define ALLOC(x) (struct x *) ckalloc(sizeof(struct x))

そして、次のように使用されています。

struct foo { ... };
p = ALLOC(foo);

素晴らしい!どこで見つかるかに注目するのは興味深いです。/usr/src/cmd/f77/defines:38と/usr/src/cmd/make/defs:38です。少し怪しいです。前者はFortran 77コンパイラの一部で、後者はmakeです。Stuart Feldmanは、これら両方のプログラムに取り組んだ人物です!その時点で複数の人々がシステムに取り組んでいたことは明らかです。残念ながら、そのようなマクロは他のどのプログラムでも使用されていません。

V7は、assert()マクロも初めて見つかるエディションであり、当時も広く使用されていました。よくやった!また、manページの数も大幅に増やしました。豆知識:私は47年前のページに対して$ man V7/usr/man/man3/foo.3を実行しましたが、一発で正常に動作しました。

V8にジャンプしましょう。1985年2月リリース、最初のC標準の4年前です。/usr/src/cmd/ideal/memut.c:7で、このワイルドなマクロが見つかります。

char *fooalloc;
#define	tryalloc(new,kind)	\
    if (!(new =(kind *) malloc(sizeof (kind)))) {	\
    emergency ();	\
    if (!(new =(kind *) malloc(sizeof (kind)))) {	\
        fprintf (stderr, "ideal: Out of space\n");	\
        exit (1);	\
        }	\
    };	\
    for (fooalloc = (char *) new;	\
        fooalloc < ((char *) new) + sizeof (kind);	\
        fooalloc ++)	\
        *fooalloc = '\0';

もし誰かがマクロの使用を厳しく批判するなら、これを見せてあげてください。使い方は少し異なります。

struct foo *x;
tryalloc(x, struct foo);

デフォルトで割り当てられたものをゼロ初期化しているため、含めることにしました!また、より見慣れたmalloc()が使用されていることもわかります。どうやら、私たちが知っているより伝統的なmalloc()/free()インターフェースに切り替わったのはこのエディションのようです。その理由を最後に仮説として述べます。

しかし、さらに興味深いことがあります。 V8では、これらのマクロも見つかります。

#define new(type) ((type *) alloc (sizeof (type)))
#define new(t) ((t *) alloc (sizeof (t)))
#define	new(s)	(s *)alloc(sizeof (s))

これらの定義を見たとき、私は椅子から飛び上がりました。基本的に私もこのように定義するでしょう!それらは次のように見つかります。

それぞれ。

残念ながら、これらのいずれかの作者を見つけることはできませんでした。

Cfront

V8では、Cfront、別名中世のC++も見つかります。したがって、Stroustrupが上記の макроからインスピレーションを得て、newを言語レベルの機能として組み込むことを決定したと考えるのは合理的です。しかし、『A History of C++: 1979−1991』の中で、彼は次のように書いています。

Simulaでは、クラス型のローカル変数またはグローバル変数は持てません。つまり、クラスのすべてのオブジェクトはnew演算子を使用してフリーストアに割り当てる必要があります。

へえ。つまり、C++のnewは「Simula」というプログラミング言語からインスピレーションを得たのです。上記のマクロも同様にそこから派生した可能性が高いです。しかし、Simulaは70年代でさえかなり古く、ましてや1985年ではありません。おそらくStroustrupは人々を説得したか、自分でこれらのマクロを書いたのでしょう。彼は後に次のように書いています。

メモリをフリーストアから割り当て、コンストラクタを呼び出して初期化を確実にするnew演算子はSimulaから借用されました。delete演算子は、C with Classesがガベージコレクタに依存しないようにするために必要な補完でした。new演算子の引数は次のように要約できます。あなたは次のように書く方が良いですか?

X* p = new X(2);

それとも

struct X * p = (struct X *) malloc(sizeof(struct X));
if (p == 0) error("memory exhausted");
p−>init(2);

そして、どちらのバージョンで間違いを犯しやすいですか?反対意見は、当時かなりの議論がありましたが、「本当に必要ない」「誰かがnewを識別子として使うだろう」というものでした。どちらの観察ももちろん正しいです。演算子newを導入したことで、フリーストアの使用がより便利でエラーが発生しにくくなりました。これにより、その使用はさらに増加し、newを実装するために使用されたCのフリーストア割り当てルーチンmalloc()が、実際のシステムで最も一般的なパフォーマンスのボトルネックになりました。 [...] 実際のプログラムがmalloc()で50%以上の時間を費やすというのは許容できませんでした。

(強調は私によるものです:P)

彼は説得力のある議論をしており、私も同意しますが、実質的に同じことを行っていたnew()マクロについては言及していません。V8では後者の冗長な方法を使用しているプログラムが多く見られますが、これらのマクロの作者が誰なのか、さらに知りたいです!

Sims

これは大きな括弧です。なぜなら、Simulaがどのようにnewを思いついたのか知りたかったからです。Wikipediaによると:

Simula 67は、オブジェクト、クラス、継承とサブクラス、仮想プロシージャ、コルーチン、離散イベントシミュレーションを導入し、ガベージコレクションを特徴としていました。

そして、それは1967年に登場しました。すごい!SIMULA Iの1964年のレポートには、次のように書かれています。

[...] newというシンボルは、アクティビティblah blah blah内の識別子Aの曖昧さを解消するためだけに機能します[...] 他の場所ではnewシンボルは冗長ですが、可読性を向上させるためにしばしば有利に使用できます。

つまり、文法的な曖昧さを解消するためだけに使われていたようです(私の理解では?)。そしてStroustrupはそれを再利用しました。賢い!

さらなる読書

The History of Simula、Jan Rune Holmevik著。まるで(悪い)SFのようです。政治的なくだらない話はさておき、最も興味深かったのは、コンパイラを作成しようとする前に言語を完全に仕様化したことです。彼らは最初のコンパイラの作成を少なくとも1年後に開始しました。これは今日では狂っているように聞こえるでしょうが、当時はコードを書くコストが大幅に高かったのでしょう。

また:The development of the SIMULA languages、著者による。私はそれを読んでいませんが、読むべきです...

Simulaがいかに影響力があったかは、本当に驚くべきことです。「10.5 Operating Instruction」では、「run time system」という用語さえ使用し、私たちが慣れ親しんでいるのと同じ意味を与えています。

Simulaは、その番、SIMSCRIPT(笑わないでください)から部分的にインスピレーションを得ました。その1962年のレポートをざっと見ても、newの言及は見つかりません。しかし、CREATEは見つかります。

CREATEステートメントによって新しいEntityが作成されると、Definition Formに指定された量の属性値を格納するためのスペースが割り当てられます。このレコードには識別番号が与えられ、CREATEステートメントで指定されたVariableは自動的にこの番号に設定されます。

例えば、CREATE DOG CALLED FIDOというステートメントは、DOGの属性に必要な量のストレージを割り当て、FIDOを属性レコードの識別番号に設定します。

魅力的です。SIMSCRIPTのレポート全体が実際、魅力的で、私はそれをわずかに skimming しただけです。冒頭では、私の理解では、プリミティブなECSのようなものさえ説明しています。しかし、最後では現実に戻されます。

[画像: SIMSCRIPTのコンパイルと実行]

SIMSCRIPTのコンパイルと実行

そして、それが何年だったかを思い出させます。

後期の版

V9の/jtools/src/sam/sam.h:14、Rob PikeによるSamエディタの一部には、new()の「一般化」バージョンがあります。

#define	new(t, n)	((t *)alloc(((ulong)(n)*sizeof(t))))

しかし、全体として、ほとんどのプログラムはこれを行いません。ほとんどはmalloc()とその仲間を直接呼び出し、各呼び出しサイトでサイズ計算とエラーチェックを行っています。それがどれほどエラーを起こしやすいかを考えると、なぜかはわかりませんが、おそらく慣性のためでしょう。V10でも同様の話ですが、V10はかなり大きいため(中世のGCCが含まれています!)、それほど徹底的にはチェックしませんでした。

何が起こったのか?

彼らはアリーナに非常に近かったのです。線形割り当て、明示的なメモリ領域、巻き戻し、さらには便利なnew()マクロまで。これらはすべて現時点では推測ですが、おそらくその一部は正しいでしょう。まず第一に、ほとんどのプログラムは取るに足らないオブジェクトのライフタイムを持っていました。メモリを割り当て、データを読み込み、処理/計算して結果を出力します。そのために、規律あるメモリ割り当て戦略は必要ありません。プロセスが終了したときにOSが回収するとしても、すべてにmalloc()を使用することさえできます。初期のmalloc()は非常にシンプルだったため、ボトルネックにもならなかったでしょう。「単一のタスクをうまくこなす」というプログラムの哲学もこのパターンに合っていました。

第二に、彼らが非常に限られたメモリでやりくりしていたことが明らかです。そして(当初は)仮想メモリもありませんでした。今日では、いくらでも大きなメモリ領域を「割り当て」ても、おそらくすべて問題ないでしょう。彼らは実際にはそうできませんでした。そのため、空きブロックやギャップをすべて回収するために奮闘し、プログラムのスペースを実際に必要な量に合わせてスケーリングしていました。しかし、これには反論もあります。コードによっては混乱しているからです。固定サイズのバッファ/配列が、どこからともなく現れ、プログラムが失敗することがあります。なぜかはわかりませんが、おそらくそれらのケースでmalloc()を使用する複雑さをそれほど価値があると考えなかったか、「壊れていなければ直さない」という考え方だったのかもしれません。

そして明らかに、単純な線形割り当てで済む場合は、そうしていました。

第三に、彼らは手動のメモリ管理を一切行うことにうんざりしていたのではないかと疑っています。malloc()インターフェースから明示的なメモリ領域を削除することは、表面上はさらに物事を単純化する変更です。実装側を賢くすれば、coremapやswapmap、anymapを渡す心配をする必要がなくなります。覚えておいてください:プログラムや問題へのアプローチ方法は、複雑なオブジェクトのライフタイムにつながりませんでした。そして、絡み合ったライフタイムに対処する必要があったとき、malloc()/free()は最初は欺瞞的にシンプルに見えました。

最後の点は証拠というより「雰囲気」ですが、実際には証拠もあります。V7ではgalloc(3)が追加されました。

GALLOC(3)                                               GALLOC(3)

NAME
     galloc, gfree, garbage - storage allocation with garbage
     collection

SYNOPSIS
     char *galloc(n)
     unsigned n;

     gfree(p)
     char *p;

     garbage( )

DESCRIPTION
     These functions perform heap storage allocation with garbage
     collection.

     Galloc allocates a block of at least n bytes and returns a
     pointer to it.  Gfree frees a block previously allocated by
     galloc.

     When space gets tight, garbage blocks are freed automati-
     cally.  A block allocated by galloc is deemed to be garbage
     unless it is reachable.  A reachable block is one whose
     first byte is pointed to by a declared C variable or by a
     pointer in a reachable block.

そして、Rob Pikeは/jtools/src/sam/sam.h:15で次のように述べています。

#define	gcnew(p, n)	((void)gcalloc((ulong)(n)*sizeof((p)[0]), (long **)&(p)))

ハッ、神話は崩壊した!作者やCに最も近い人々はガベージコレクションを望んでいました。コンピューターの速度がどれほど速いか(そしてそのペース)を考えると、これは理にかなっています。LimboやGoで後に行ったことを見れば、さらに明らかです。彼らは、プログラムを書くためのより高レベルの汎用言語を望んでいました。彼らにはCがあったので、その上にGCをボルトで留めようとしました。

最後に、これらの問題の多くは当時すでに知られていたことを指摘すべきです。『The Standard C library (1992)』で、P. J. Plaugerは次のように書いています。

ヒープ上のデータオブジェクトを任意の順序で割り当てたり解放したりすると、必然的に一部の割り当てられたデータオブジェクトの間に使用できない穴が残ります。これもヒープの使用可能サイズを低下させます。

この知識に過剰反応しないでください。関連するデータを構造体にまとめ、すべて一度に割り当ててください。これによりヒープのオーバーヘッドは最小限に抑えられますが、それは良いプログラミングスタイルでもあります。ヒープのオーバーヘッドを節約するためだけに無関係なデータを集めないでください。同様に、ライフタイムが似ているデータオブジェクトをすべて一度に割り当て、それらをほぼ同時に解放してください。これによりヒープの断片化が最小限に抑えられますが、それも良いスタイルです。断片化を最小限に抑えるためだけに、無関係なヒープ操作を進めたり遅延させたりしないでください。ストレージ割り当て関数は、プログラミングの柔軟性を高める重要な助けとなります。それらが意図されたとおりに使用してください。

私の批判:単に「正しいことをする™」と言うのは、それをどのように行うかを示したり、ツールを提供したりするよりもはるかに簡単です。

結論

わかりません。良いアイデアに注意を払ってください。そうしないと、失われてしまうかもしれません?(文字通りV4の場合は、笑)陳腐に聞こえます。

また、「Cオブジェクトコード改良ツール」は、Cがどのようにか魔法のように速かったという幻想を打ち砕きます。実際、Ritchieは後に式オプティマイザを追加し、各エディションで両方を拡張し続けました。Cは単に遅くはなく、コンパイラ(そしてそれでも、コンパイラがあまり賢くなかったためregisterを追加する必要がありました)に注がれたエンジニアリング努力によってのみ高速化されたのだと思います。Unixが急速に広まり、Cもそれに伴ったため、ハードウェアメーカーはCに順応したため、おそらく高速であり続けました。Mike Pallによる興味深いエッセイでは、現代のCPUがコンパイラ(コンパイラが最適化に非常に努力した)によってコンパイルされたCコードの不変条件を推測するためにどれほど懸命に努力しているか、そしてそれに伴うコストについて議論しています。

いつか、InfernoとPlan 9についても調査したいと思っています。BSDも同様かもしれません。それまで...

またね!

~/V8$ grep -Rn <redacted> .
./usr/src/cmd/c2/c21.c:660:			if (p->op==ASH && preg==(regs+RT1+1)) cp2[-1]=BYTE; /* stupid DEC */
./usr/src/cmd/c2/c22.c:11:/* cursed be the preprocessor, whose impotence and stupidity
./usr/src/cmd/adb/main.c:16:int xargc; /* bullshit */
./usr/src/cmd/ccom/vax/gencode.c:644:	case LS: /* stupid vax */
./usr/src/cmd/ccom/vax/gencode.c:664:		if(incrsize(p) != 4) { /* stupid vax */
./usr/src/cmd/ccom/vax/gencode.c:705:		 * instead, generate crummy code (stupid assembler)
./usr/src/cmd/ccom/vax/genaux.c:598:	op->in.op -= (ASG 0); /* crap for crap */
./usr/src/cmd/ex/ex.c:352:		 * Don't have to be upward compatible with stupidity
./usr/src/cmd/ex/exrecover.c:492:	 * This is old and stupid code, which
./usr/src/cmd/ex/ex_put.c:965: * This is so idiots who make esc their start/stop don't lose.
./usr/src/cmd/upas/send/send.c:288:	/* check forwarding (what anti-social crap!)
./usr/src/cmd/uucp/Makefile:2:# this makefile is full of crap
./usr/src/cmd/rarepl/rct.h:2: * first block of rct: random crap
./usr/src/cmd/sdb/machine.h:16:struct {short hiword; short loword;}; /* stupid fp-11 */
./usr/src/libc/gen/regcomp.c:26:/* predeclared crap */
./usr/sys/dev/uda.c:951:	register u_short *sp; /* for sdi crap */
./usr/sys/dev/uda.c:1123:		 * should do read-after-write crap
./usr/sys/dev/flp.c:206:			 * We're really stupid today - call it an
./usr/sys/dev/kmc.c:9:/* nonstandard crap */
./usr/sys/dev/vaxcpu.c:36: * Information to patch around the stupidity of configuration
./usr/sys/sys/sys1.c:488:		else /* stupid 1k 0413 files */
./usr/sys/sys/vmproc.c:238: * when large procs stupidly fork() instead of vfork().
./usr/sys/boot/bb/bblock.s:117:	movl	r5,r11	# put boot flags in stupid bky place
./usr/sys/boot/bb/bblock.s:118:	movzbl	$DEVNUM,r10	# more berkeley bullshit - 7 == uda50
./usr/sys/boot/bb/1k.s:2:	.set	DEVNUM, 8	# stupid hack

(間違いがあれば、親切に教えてください :))