トップページ

Linux 日記
  Linux日記08~
  Linux日記2006
  Linux日記2005
  Linux日記2004
  Linux日記2003
  Linux日記2002
  Linuxカーネル
  + メモ ... (3p)
  + 勉強会 ... (6p)

アートでいこう(笑)!
  イラスト日記08〜
  イラスト日記2005
  イラスト日記2004
  イラスト日記2003
  アイコン描こうぜ

UCC(梅丘自転車部)
  活動ログ2003
  活動ログ2001
  活動ログ2000
  その他
  ワシの愛車

自動車関連
  FD3S日記05~
  S202日記2004
  S202日記2003
  S202日記2002
  S202給油等記録
  S202掲示板

自宅前カメラ
EZweb/PCSV用
プロフィール
社会科ノート 2004
社会科ノート 2003
全角半角変換
自家用リンク
視聴予定表
メモ

W/O index
Linux日記2002

シンボルのバージョン(Mon Dec 09 2002)

$ readelf -s /bin/bash | grep "@"

とやると、わらわらと出てきます。で、これは何?

ld の info から "VERSION" の項


モバイる?(Mon Nov 18 2002)

携帯の端末のバッテリがヘロヘロになってしまったのを機に、キャリアもかえてみようかと思って、各社のパンフをもらってきて検討していました。やはり AirH" のつなぎ放題は魅力ですからね。で、Linux だけど、どうなのよ? できれば USB でね。今まで Docomo の 1.5MHz(シティフォン)では KDD の UPA-9664 を、USB Serial (usbserial.o) で使っていました。どうやら「CDC (Communication Device Class 1.1)」なるものに対応していれば usbserial.o で使えるようです。この手のは、平気で最新 OS(MacOS X とか)にも対応しているのも特徴。ただし、ハードに手がかかっているんでしょう、高価な傾向です。というわけで、サン電子AX64LX


Java(Wed Oct 30 2002)

現実逃避ぎみにオープンソースの Java 環境をさぐっいました。Jikes(超っ速コンパイラ)と Kaffe(GPL の VM とランタイム)の RPM パッケージをほげってみました。

Java で今風になるぞ〜。


無題(Mon Oct 28 2002)

てなわけでイマイチすっきりしないので、Amazon.co.jp で「Linkers & Loaders」を購入しました。

では。


ダイナミックリンクを追え!(Fri Oct 04 2002)

以下、ELF の仕様書は必携。

PLT (Procedure Linkage Table) は、ダイナミックリンクのためのスタブコードのかたまりで、プログラムヘッダでは、読み出しオンリー実行可に入っています(ユーザコードと同じ。objdump で逆アセンブルすれば見える)。ユーザのコード側からは、直接にはこれらのスタブが呼び出されます。テーブル、とは言うけれど、各エントリの長さが違うこともあるので、配列っぽくはないです、たんなるカタマリです、ラベルもないようだし。GOT (Global Object Table、だっけ?) に、実コード(DLL の)へのポインタが入っています(プログラムヘッダ的には、読み書き可能実行不可。当然実行時にしかない)。初回のコール以前には、GTO のエントリは、スタブの余り部分に制御を跳ばすようになっています(説明しづらいんだけど、gdb で実行をとめながら、逆アセンブルしたり /proc/*/maps を見ていればすぐ分かりますよ)。

道具だては、readelf(1), gdb(1) (list, disassemble, x/x あたりのコマンドを駆使して、実行中の環境下でコードを追うのだ)。

「スタブコード(stub codes)」とは、あるコードが別のコードを呼ぶ際に、仲介を行なうコードのこと。

_dl_runtime_resolve@/lib/ld-linux.so.2 が、「初回の呼びだし時点で」テーブル(GOT)を準備。


GLIBC で、ld.so だけコンパイルする(Sun Sep 22 2002)

備忘録として。これができんと、オチオチとデバッグコードもはさめない。

  1. まず、普通にビルド(configure & make)する
  2. "cd elf" で ELF ディレクトリへ
  3. "OBJDIR=../build-i386-linux" で、オブジェクトの入るディレクトリを指定
  4. "make objdir=$OBJDIR $OBJDIR/elf/ld.so" でビルド

難解ホークス。


ELF も腑分けじゃ(Fri Sep 20 2002)

トールキン先生、ごめんなさい。さて、ELF フォーマット(仕様書の PDF はこちら、HOWTO はこちらを参照)について概観します。

ELF ファイルの先頭には ELF ヘッダがあり、そこに二つのテーブルのオフセット位置が書かれています。一つはセクションヘッダのテーブル、今一つはプログラムヘッダのテーブルです。

前者は、実データである、一つ以上のセクションのメタデータですから、理解は容易でしょう。問題はプログラムヘッダ。

以下、カーネルの、ELF の実行部を見ていきます。load_elf_binary() で、まずELF ヘッダのマジックナンバーが ELF のそれと同一であることのチェックをはじめ、各種のチェックが入ります。それらをパスしたら、ELF ヘッダに従って、プログラムヘッダを読み込みます。つづいて、エントリタイプが PT_INTERP なプログラムヘッダを探します。見つかったならば、elf_interpreter に、ELF インタプリタの名前を保持します。

ダメだこりゃ、疲れる。自動化しないとやっておれん。


exec(2) とスタティックリンク、そしてダイナミックリンク(Thu Sep 19 2002)

まずきわめておおざっぱに、カーネルの exec() が実行可能ファイルを実行するときにどのように働くかを順に書くと、

  1. ユーザ空間から exec() でカーネル空間へ入る
  2. 実行可能ファイルを mmap() し、スタックを新調する
  3. 古いのを munmap() して、スタックも捨てる
  4. ユーザ空間の SP に、新調したスタックをポイントさせる
  5. ユーザ空間の IP にエントリポイントをポイントさせる
  6. ユーザ空間に帰る

というようになります。この際、細かいことは気にしないでください。

<comment>"binprm" stands for "BINary PaRaMeter"</comment>

でだ、実行可能 ELF バイナリには上記 "5" で用いられるエントリポイントが書かれています。readelf(1) コマンドで見られます。

$ readelf /bin/bash -a | grep "Entry point address" Entry point address: 0x805a250

さらに、ストリップかけていないバイナリであれば、このアドレスに対応する、以下のようなシンボルの情報も読みとれるはずです。

$ readelf /bin/bash -a | grep 805a250 876: 0805a250 0 FUNC GLOBAL DEFAULT 13 _start

この "_start" はどこから来たかと言えば、gcc で "--verbose" オプションつければうかがえます。コレ↓です。

$ nm /usr/lib/crt1.o | grep -e '\<_start\>' -e '\<main\>' 00000000 T _start U main

御覧のように、_start は、コード込みのグローバルなコード(T)であり、この段階では未定義な main を外部に求めています。きっとこのふたつをつないでくれるのでしょう。ld(1) は、デフォルトのエントリポイントを "_start" とし、"-e" もしくは "--entry" オプションで変えられます(binutils の ld/ldlang.c 参照、'"start"' で検索してみましょう。カーネルの arch/i386/Makefile には "LDFLAGS=-e stext" とあり、vmlinux に readelf かければ、エントリになっているのがわかる)。この場合 /usr/lib/crt1.o は glibc 由来なんですが、たとえばオルタナティブである uClibc の場合には別の初期化コード(crt0.o)があります。

で、スタティックの場合は単にこのエントリからコードがスタートするだけなのですが、ダイナミックの場合(".interp" セクションがある場合)は、カーネルが直接起動するのは、そのセクションに名前を書かれた「インタプリタ」と呼ばれるプログラムで、最近の ELF なバイナリならば /lib/ld-linux.so.2(実行可能)です。このセクションをもともと含むのは、glibc のダイナミックリンクライブラリ /lib/libc.so.* です(glibc の elf/interp.c の __invoke_dynamic_linker__ から)。スタートアップコードに入っていないのは、それらはスタティックでも使うからです。

$ readelf /bin/bash -a | grep -C ld-linux PHDR 0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4 INTERP 0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x8a900 0x8a900 R E 0x1000 LOAD 0x08a900 0x080d3900 0x080d3900 0x05c50 0x0a4b0 RW 0x1000

さて、この /lib/ld-linux.so.2 がどこから来るかと言うと……うーん glibc のビルドシステムがよくわからないので不明です。elf/ ディレクトリの下だとは思うんだけど……あった! elf/rtld.c の RTLD_START マクロは sysdeps/(ARCH)/dl-machine.h で定義されていて、ここにエントリポイント "_start" があります。ld-linux.so.2 は、必要なライブラリをすべてロードしたのち、あらためてホンチャンのエントリポイントにジャンプをかけます。

<comment>"CSU" stands for "C StartUp"</comment>

では次回、ELF の解析でも。


/sbin/SuSEconfig の謎なんぞ(Fri Sep 13 2002)

要は設定ツールである YaST との談合なんですが、

  1. YaST で、SuSE 式の設定ファイルに設定を書きこむ
  2. YaST 終了時に /sbin/SuSEconfig が呼ばれる
  3. すると、SuSE 式の設定ファイルは本チャンの設定へ変換される

当然出力された設定ファイルはリードオンリー。手で書きかえても、次回の /sbin/SuSEconfig で上書きされるだけです。気をつけましょう。

その書きかえや反映をリブート時まで遅らせたい、とか、特定の設定モジュールだけ起動したい、などの設定もできます。詳しくは /etc/rc.d/boot や /sbin/SuSEconfig を読んでください。


SuSE の init スクリプトはえらい(Fri Sep 13 2002)

/etc/init.d/skeleton(sysinit スクリプトのスケルトン)を読むとよくドキュメントされているんですが、SuSE システムのスクリプトには、init スクリプト間の依存関係を書けます! Turbo では、依存関係を人が解決して実装時に即値で起動順序を決定していましたが、SuSE では chkconfig(8) が動的に順序づけをします。しかも、強い依存関係(絶対 ON)と、弱い依存関係(ON ならばお先にどうぞ)まで指定できる! えらい! ついでに、chkconfig(8) のサブで動いている insserv(8) の機能で、疑似の名前での依存関係もできますので、/etc/insserv.conf に目をとおしておきましょう。

一方 Turbo では、/etc/sysconfig/security ファイルで決定される敷居値によって、セキュリティ設定ごとに全体のおおざっぱなオン・オフを制御する機能があったんです。これほしい。


SuSE のスタートアップを腑分けじゃ(Thu Sep 12 2002)

訳あってここしばらく SuSE 系の環境で開発をしているのですが、妙ですねえ。何が分からないって、同じ RPM を用いたディストリビューションでありながら、なぜこうもコアのシステムが異なってしまったのかが分かりません。とは言え、Linux は Linux です。用いているコンポーネントもほぼ同じである以上、理解はそう大変なはずはありません。というわけで、スタートアッププロセスを追ってみます。

カーネルのスタートアップの最後は、スレッド分けして /sbin/init を exec(2) し、元スレッドはアイドルするだけです。で、そんな init(8) のプロセスが、以下。

$ ps ax | awk '($1 == 1) { print }' 1 ? S 0:08 init

init(8) は /etc/inittab に従って、「普通の」Linux 環境が動作する限り、初期化・終了のプロセスと、ターミナルの管理をしてくれるわけだ(組み込みやインストーラのような特殊な環境ではその限りではないので、「普通の」と断ったわけですが)。うーん、久しぶりに見ると inittab の読み方を忘れている。inittab(5) の man を見れば書いてあります。init(8) によると、ランレベルって 0〜6 と s または S の 8 つだそうですが、ホントか? そんなときはオープンソース、ソースにあたる。

$ rpm -qf /sbin/init sysvinit-2.82-177

うん、このへんからして Turbo と名前が違う。あとでわかるんですが、違うのは名前だけではなく、様々な独自の機構が仕込まれています。さすが SuSE、UNIX を熟知しています。では sysvinit のソースをあたります。展開して、src/init.c:read_level() に以下のようにあります。

if (islower(foo)) foo = toupper(foo); if (ok < 1 || ok > 2 || strchr("QS0123456789ABCU", foo) == NULL) { log(L_VB, "bad runlevel: %c", foo); return(runlevel); }

なんでー、ウソばっか、いっぱいあるじゃん。"QS0123456789ABCU" の 16 文字。しかも、大文字小文字は区別しません。さらに言うと「前のランレベルなし」を意味する "N" もあるようで。

char prevlevel = 'N'; /* Previous runlevel */

おっと、init(8) をよく読むと書いてありますね。"s または S は 1 のエイリアス、7-9 も仕様できるが、伝統的に使われない" と。どっちにしてもゴリゴリハードコードなコマンドであって、あんまりキレイなもんではないのね。

まあいいや。で sbin(8) は最初に、/etc/inittab を見て "initdefault" アクションのついたランレベルに突入する、わけですが、man によると "はじめてマルチユーザモードに入るときには、boot および bootwait アクションのエントリを実行" だそうです。コード追い切れなくて sysinit との差が不明なんですが、まあそうなんでしょう(可読性低し!)。それら boot 系のエントリは、以下の行だけ。

si::bootwait:/etc/init.d/boot

Turbo では /etc/rc.sysinit なんす。では /etc/init.d/boot 読んでいきましょう。まず、こんなの読んでます。

. /etc/rc.status . /etc/sysconfig/boot

一つめは rc 系のスクリプトが共通に使う文字列やファンクションですね。だいたい分かるからいいや。ふたつめは、ブートの挙動の設定ですね。これに続いて、proc や /etc/fstab に従ったファイルシステムのマウントを行ないます。

SuSE には /sbin/blogd なるものがありますね。なになに、どうやらシステムコンソール(/dev/console)に仮想端末をつないで、出力をフックして /var/log/boot.msg に追加するようですね。で、そのデーモンたちあげたあとに、/etc/init.d/boot.d/ 以下のコマンドを実行しますが、それが済んだら blogd は落されます。てことは、その間のログは上記ファイルを見ればよいのね。

ここで SuSE 特有の YaST2 が出てきます。でもどうやら、初回起動時にしか実行されないようです。何してたんでしょうねえ。こういう謎なところが、ちょっと SuSE のヤなところ。

さて、ようやくデフォルトにはいれます。以下、3 ね。

l3:3:wait:/etc/init.d/rc 3

Turbo では /etc/rc でした。/etc/init.d/rc へ、第一引数に 3 をつけて実行しているのですが、どうやらこの引数見てないね(Turbo では見てた)。なぜなら、init(8) が子プロセスを実行するときには $RUNLEVEL(現在のランレベル), $PREVLEVEL(その前までのランレベル)などの環境変数を設定するからです。こっちのほうがアテになるしね。

あ、また blogd 上げてる。

で、あとは普通どおりに $PREVLEVEL ディレクトリ(/etc/init.d/rc${PREVLEVEL}.d/)内の "K" つきコマンドを落として、$RUNLEVEL ディレクトリ(/etc/init.d/rc${RUNLEVEL}.d/)内の "S" つきコマンドを上げます。

あとは getty を上げて、おしまい、と。

1:2345:respawn:/sbin/mingetty --noclear tty1 2:2345:respawn:/sbin/mingetty tty2 3:2345:respawn:/sbin/mingetty tty3 4:2345:respawn:/sbin/mingetty tty4 5:2345:respawn:/sbin/mingetty tty5 6:2345:respawn:/sbin/mingetty tty6

ふー。


ヒープ管理のサンプルコード(Thu Jul 25 2002)

ヒープ管理のサンプルコード(heap.c)です。ほぼそのまま「C プログラマのためのアルゴリズムとデータ構造 Part 2」(近藤嘉雪著・ソフトバンク刊)からの抜粋ですが。


文字列型各種 in C(Wed Apr 17 2002)

# 再録なので日付さかのぼっています

C 言語で、文字列クラスを定義してみましょう。以下の三種類を実装します。

  1. 構造体を用いた、オーソドックスなもの(インタフェイス str.h, 実装 str.c, テストコード str_test.c
  2. 文字列へのポインタを用いた、お手軽なもの(インタフェイス str_ptr.h, 実装 str_ptr.c, テストコード str_ptr_test.c
  3. マクロを用いた、さらにお手軽なもの(マクロ定義 str_cpp.h, テストコード str_cpp_test.c

なお、malloc(3) による領域確保にまつわるエラーの処理は、今回は行なっておりません。それはまた、別の機会に。

1. 構造体を用いた、オーソドックスなもの

最初に挙げる文字列クラスは、C 言語においてクラスを実装する際にもっとも一般的と思われる、構造体を用いる手法で実装されたものです。まず最初に、テストコードと実行結果を見てみます。テストコードは、「コマンドに与えられた引数をすべて連結して表示する」という簡単なものです。

# 余談: この手のクラスを C++ でラッパーするのは、赤子の手をひねるより簡単。ていうか、楽しい

テストコード (str_test.c)
 1: #include "str.h"
 2: 
 3: #include <stdio.h>
 4: 
 5: int main (int argc, char * * argv) {
 6:   Str str;
 7:   int i;
 8:   char * pszDelim;
 9:     str = Str_new("argv: ");
10:     pszDelim = "";
11:     for (i = 0; i < argc; i ++) {
12:         Str_appendf(str, "%s%s", pszDelim, argv[i]);
13:         pszDelim = ", ";
14:     }
15:     printf("%s\n", Str_get(str));
16:     Str_free(str);
17:     return (0);
18: }
19: 

実行結果
$ gcc -Wall -o str_test str_test.c str.c
$ ./str_test hoge fuga HelloWorld
argv: ./str_test, hoge, fuga, HelloWorld
$ 

テストコードには、"Str" というクラスがあり、クラスメソッドとしては、コンストラクタの "Str_new()"、デストラクタの "Str_free()"、フォーマット文字列を連結するメソッド "Str_appendf()"、構築された文字列を文字列ポインタとしてとり出すメソッド "Str_get()" が用いられています。重要なことは、クラスを利用する側からは、実装の詳細が窺えないようになっているということです。

インタフェイス (str.h)
 1: #ifndef STR_H
 2: #define STR_H
 3: 
 4: #include <stdarg.h>
 5: 
 6: struct StrImp;
 7: typedef struct StrImp * Str;
 8: 
 9: Str Str_new(const char * psz);
10: void Str_free(Str self);
11: void Str_appendf(Str self, const char * pszFormat, ...);
12: const char * Str_get(Str self);
13: 
14: #endif /* STR_H */
15: 

テストコードで用いられていたクラスとメソッドが宣言されています。各メソッドは、コンストラクタ(Str_new())が返した Str クラスのインスタンスを第一引数にとります。この形式は、POSIX における、クラス FILE と コンストラクタ fopen(3)、メソッド fprintf(3)、デストラクタ fclose(3) などの関係と同じです。「クラスベースのオブジェクト指向実装である」という自覚はなくても、ごく一般的に用いられている手法なのです。

注意して欲しい点は、このクラスを利用する側(つまり、ここでは上記のテストコードの実装者)が知ることができるのは、このインタフェイスまでである、ということです。言いかえると、このクラスの実装(str.c)に依存したプログラムを書いてはいけない、ということです。一般に、インタフェイスは長期にわたって安定していますが、実装は頻繁に改変されることがあります。そのような場合、実装に依存した書きかたをしていると、利用する側も、それに伴って、改変や再コンパイルを要求されることになります。

その一例。"struct StrImp" は、構造体の「名前の宣言だけ」をしています。このインタフェイス宣言ファイルの中では、"struct StrImp" はポインタとしてしか用いられていないので、コンパイラは、そのサイズを、ポインタのサイズとして扱うことができます。ここでもし "Str_get()" メソッドがなかったとしたら、クラスの利用側に、文字列を取得するために内部実装をさらさなければなりません。この場合は、str.c に入っている構造体の定義を、str.h で行なわなければならなくなります。その結果、インタフェイスと実装の分離ができなくなります。

実装 (str.c)
 1: #include "str.h"
 2: 
 3: #include <stdlib.h>
 4: #include <string.h>
 5: #include <stdio.h>
 6: #include <stdarg.h>
 7: 
 8: struct StrImp {
 9:     char * psz;
10: };
11: 
12: Str Str_new (const char * psz) {
13:   Str self = (Str) malloc(sizeof (struct StrImp));
14:     self->psz = strdup(psz);
15:     return (self);
16: }
17: 
18: void Str_free (Str self) {
19:     free((void *) (self->psz));
20:     free((void *) self);
21: }
22: 
23: const char * Str_get (Str self) {
24:     return (self->psz);
25: }
26: 
27: void Str_appendf (Str self, const char * pszFormat, ...) {
28:   va_list args;
29:   size_t sizeRequired;
30:     va_start(args, pszFormat);
31:     sizeRequired = vsnprintf((void *) 0, 0, pszFormat, args) + 1;
32:     self->psz = (char *) realloc((void *) (self->psz),
33:      strlen(self->psz) + sizeRequired );
34:     vsnprintf((self->psz) + strlen(self->psz), sizeRequired,
35:      pszFormat, args );
36: }
37: 

ここではじめて、実際に文字列を格納する場所となる、"struct StrImp" のメンバ変数 psz が登場します。この実装では、文字列へのポインタを利用して、文字列を保持します。この部分はライブラリの利用者側からは見えないので、あとで「やはり配列で実装したい」とか「文字列はリレーショナルデータベースに格納したい(ってなことは実際にはないだろうが)」などの必要が生じた場合には、インタフェイスを変更することなしに、実装を変更することができます。

2. 文字列へのポインタを用いた、お手軽なもの

以下のふたつの実装は、亜流です。文字列型を用いるのに、いちいち構造体なんぞ作っとるのはまだるっこしい、という場合に使いましょう。ただし、当然のことながら柔軟性と拡張性は犠牲になります。

テストコード (str_ptr_test.c)
 1: #include "str_ptr.h"
 2: 
 3: #include <stdio.h>
 4: 
 5: int main (int argc, char * * argv) {
 6:   char * psz;
 7:   int i;
 8:   char * pszDelim;
 9:     psz = Str_new("argv: ");
10:     pszDelim = "";
11:     for (i = 0; i < argc; i ++) {
12:         Str_appendf(& psz, "%s%s", pszDelim, argv[i]);
13:         pszDelim = ", ";
14:     }
15:     printf("%s\n", psz);
16:     Str_free(& psz);
17:     return (0);
18: }
19: 

さきほどの、構造体を用いた例とは違って、メソッドの第一引数にわたしているのが、文字列へのポインタへのポインタになります。これは、メソッド側で、文字列の領域の確保と解放を行なうためです。ここが理解できない人は、もう一度教科書のポインタの項を読みなおしておいてください。

実行結果
$ gcc -Wall -o str_ptr_test str_ptr.c str_ptr_test.c
$ ./str_ptr_test hoge fuga HelloWorld
argv: ./str_ptr_test, hoge, fuga, HelloWorld
$ 

実行結果は、先の例とまったく同じです。

インタフェイス (str_ptr.h)
 1: #ifndef STR_PTR_H
 2: #define STR_PTR_H
 3: 
 4: char * Str_new(const char * pszConst);
 5: void Str_free(char * * ppsz);
 6: void Str_appendf(char * * ppsz, const char * pszFormat, ...);
 7: 
 8: #endif /* STR_PTR_H */
 9: 

文字列へのポインタのポインタをとるようになっている点以外は、とくに変わっていません。

実装 (str_ptr.c)
 1: #include "str_ptr.h"
 2: 
 3: #include <stdlib.h>
 4: #include <string.h>
 5: 
 6: char *
 7: Str_new (
 8:   const char * pszConst ) {
 9:     return (strdup(pszConst));
10: }
11: 
12: void
13: Str_free (
14:   char * * ppsz ) {
15:     free((void *) (* ppsz));
16: }
17: 
18: #include <stdarg.h>
19: #include <stdio.h>
20: 
21: void
22: Str_appendf (
23:   char * * ppsz,
24:   const char * pszFormat,
25:   ... ) {
26:   va_list args;
27:   size_t sizeRequired;
28:     va_start(args, pszFormat);
29:     sizeRequired = vsnprintf((void *) 0, 0, pszFormat, args) + 1;
30:     (* ppsz) = (char *) realloc((void *) (* ppsz),
31:      strlen(* ppsz) + sizeRequired );
32:     vsnprintf((* ppsz) + strlen(* ppsz), sizeRequired, pszFormat, args);
33: }
34: 

実装のアルゴリズムも、先の例と同じです。

3. マクロを用いた、さらにお手軽なもの

「インタフェイスだの実装だののファイルが増えるのはイヤだ。Cのソースの先頭にチョロっとコードを追加するだけで使いたい」というものぐさな気分のときに使いましょう。

テストコード (str_cpp_test.c)
 1: #include "str_cpp.h"
 2: 
 3: #include <stdio.h>
 4: 
 5: int main (int argc, char * * argv) {
 6:   char * psz;
 7:   int i;
 8:   char * pszDelim;
 9:     psz = Str_new("argv: ");
10:     pszDelim = "";
11:     for (i = 0; i < argc; i ++) {
12:         Str_appendf99(& psz, "%s%s", pszDelim, argv[i]);
13:         pszDelim = ", ";
14:     }
15:     printf("%s\n", psz);
16:     Str_free(& psz);
17:     return (0);
18: }
19: 

インクルードしているヘッダファイルが異なる点以外は、"2" のテストコードと一字一句まで同じです。

マクロ定義 (str_cpp.h)
 1: #ifndef STR_MACROS_DEFINED
 2: #define STR_MACROS_DEFINED
 3: 
 4: #include <stdlib.h>
 5: #include <string.h>
 6: #include <stdio.h>
 7: 
 8: #define Str_new(pszConst) strdup(pszConst)
 9: #define Str_free(ppsz) { free((void *) (* ppsz)); (* ppsz) = (void *) 0; }
10: #define Str_appendf(ppsz, pszFormat, args...) \
11:   sprintf(((* (ppsz)) = (char *) realloc((void *) (* (ppsz)), \
12:    strlen(* (ppsz)) + snprintf((void *) 0, 0, (pszFormat), args) + 1 )) + \
13:    strlen(* (ppsz)), (pszFormat), args )
14: 
15: #define Str_appendf99(ppsz, pszFormat, ...) \
16:   sprintf(((* (ppsz)) = (char *) realloc((void *) (* (ppsz)), \
17:    strlen(* (ppsz)) + snprintf((void *) 0, 0, \
18:    (pszFormat), __VA_ARGS__) + 1 )) + \
19:    strlen(* (ppsz)), (pszFormat), __VA_ARGS__ )
20: 
21: #endif /* STR_MACROS_DEFINED */
22: 

ごちゃごちゃしていますが、ロジック自体は "2" の例で用いたポインタを使った実装と同じです。なお、可変長引数をとれるマクロの定義("args..." の部分)は、GCC の CPP 固有の機能のようです。

追記(Wed Oct 23 2002): C99 で、晴れて可変長引数がサポートされましたので、書きかえました、Str_appendf99() の部分をね(アンダースコアつきは何となくエレガントじゃない感が強いんですが……)

実行結果
$ gcc -Wall -o str_cpp_test str_cpp_test.c
$ ./str_cpp_test  hoge fuga HelloWorld
argv: ./str_cpp_test, hoge, fuga, HelloWorld
$ 

まとめ

まとめるほどのことはないです。では。


はじめのことば(Wed Jul 24 2002)

何をどう書けばいいのかよくわかりませんが、毎日仕事で(?) Linux の各部をいじっている者もあまりいないと思うので、そんな人間の書くことは、だれかの何かの役に立つこともあるかも知れないから、思いつくこと、やったこと、備忘録などを書きつらねてみようと思います。よろしくおねがいします > 自分。

追記(Fri Sep 13 2002): 書くことがないと思ったら、自分の作業は普遍性に欠くことに気づきました。弱ったな。

- ご意見・ご要望は、サイト管理者(那賀樹一郎 (Kiichiro NAKA) <knaka@ayutaya.com>)までお願いします。
- このサイトは、Turbolinux 上の Mozilla w3m でテストされています。Internet Explorer では未確認です
- 言うまでもありませんが、当サイトはリンクフリーです
- W3C の HTML チェックをかけたところ、ズタボロでした。頑張ったけど、ダメだこりゃ……