C99を使ったオープンソース開発

あなたのCコードは標準に合っていますか?

C99とは何ですか? 誰に必要なのですか? もう入手できるのですか? Peter SeebachがLinuxやBSDシステムで使用可能な新しい機能に焦点を当てながら、1999年版のISO C標準について説明します。

Peter Seebach (dwlinux@plethora.net), Writer, Freelance

Author photoPeter Seebachは、たしかこの辺にFileメニューがあったはずだと思っています。Peterはコンピューターを使い始めてすぐに、プログラミングを始めましたが、いまだにGUIを目新しく感じています。



2004年 4月 24日

オープンソースのオペレーティング・システムと一緒に配布されているgccのバージョンにはC99の新しい機能の全てがサポートされているわけではありません。しかし大部分の機能は既に広く使われるようになっているので、特に効率や明快さが求められる所では、新しい開発にC99の機能の採用を真剣に考えて良いのではないかと思います。

この記事では、最近のLinuxやBSDでC99の言語機能やライブラリ機能がどの程度使用できるものなのかについて検討します。こうした機能の多くはgccの標準機能なので、gccの最近のバージョンは他の大部分のプラットフォームでも同じように動作します。ライブラリのサポートは当然の事ながらディストリビューション毎に、またオペレーティング・システム毎に異なります。

言語標準でgccを呼び出す

GNU Cコンパイラーはいくつかバージョンの異なるCプログラミング言語をサポートしています。使用しようとするC標準は、 -std オプションを使ってコマンドラインから選択する事ができます。デフォルトはどのバージョンでもなく「GNU C」言語となっており、これは独自の拡張を持っています。一般的なC標準のバージョンは、次のオプションで選択する事ができます。

C-9と・・・何だって?

C99標準はCのISO標準として最も新しいものです。ちょっとした歴史的背景を知っていると役に立つかも知れません。C言語は初期の頃には委員会もなく開発され、多くの変更を経ました。やがて大部分のベンダーは、KernighanとRitchieによる1978のThe C Programming Language初版に近いところに落ち着いたのですが、拡張も普通のことでした。ANSIはこの本と、既に習慣的に行われていることに基づいて標準化の作業を開始し、1989-1990には広く標準が入手できるようになりました。この標準は広く「C89」と呼ばれています。一部の人は1978年版のKernighanとRitchieによる版を、ふざけて「C78」と呼んだりしています。

次の10年の間にも、コンパイラーのベンダーは新しい拡張や新しい機能の開発を続け、最も有用で最も広くサポートされている新しい機能の多くについての長年に渡る標準化作業を反映した改訂版が、1999年にリリースされました。この標準はよく「C99」標準と呼ばれるのです。

  • -std=c89 または -std=iso9899:1990
    元々のC89標準
  • -std=iso9899:199409
    C89と、Normative Addendum 1にある変更を加えたもの
  • -std=c99 または -std=iso9899:1999
    C99で改版された標準

ある標準のバージョンに完全準拠させるためには -pedantic オプションを使います。このオプションはコードを他のコンパイラーに移植しても確実に使えるようにするために有用です。例えば、gccを使っていない人とコードベースを共用している場合には、いつもこのオプションを付けたいと思う事でしょう。注意して欲しいのですが、 -pedantic フラグは時々、指定された標準の詳細情報を間違って取得する事があるのです。例えば、C89の規則をC99プログラムに強制しようとしたり、不明な規則を強制しようとして失敗したりします。それでもやはり、テスト用に持っている価値はあります。移植可能なコードを書こうとしているのであれば、 -std=c99 -pedantic -Wall で大半はよいはずです。

C89標準は新しい概念を導入しました。自立環境(freestanding environment)とホスト環境(hosted environment)の区別です。ホスト環境は大部分の人が慣れている環境で、完全な標準ライブラリを提供し、実行は常に main() で開始されます。

自立環境を意図した、少し違った警告や振る舞いを望むのであれば -ffreestanding オプションを使います。

デフォルトはホスト環境です。よく尋ねられるFAQ項目の一つの答えを先に言うと、はいその通り、標準にリストアップされているものとは異なる引数や戻り型を持つ main() の宣言に対してgccが警告を与えるのは意図的なものです。C99標準は実装が代替宣言を提供する事を許しているのですが、代替宣言は移植可能ではないのです。特に、一般的によく行われる、 main() を戻り型 void で宣言するのは単純に不正なのです(NetBSDのカーネルが -ffreestanding フラグを付けてコンパイルされるのはこのためです)。


言語機能

C言語には2つの部分があります。紛らわしい事に、これらは「言語」と「ライブラリ」と呼ばれるのです。昔から、誰もが再利用するような、一般的に使われるユーティリティ・コードのひとまとまりがありました。これはやがて標準化され、標準Cライブラリ(Standard C Library)と呼ばれるものになりました。2つの区別は、最初は理解しやすいものでした。コンパイラーが行う場合にはそれは言語であり、アドオン・コードの中にあれば、それはライブラリだったのです。

時が経つにつれその区別が曖昧になって行きました。例えば、あるコンパイラーでは64ビット演算の外部ライブラリに対するコールを生成し、あるライブラリ機能はコンパイラーが不可思議な方法で操作するのです。この記事では、両者の区別はこの標準にある用語に従う事にします。標準の「Library」の章にある機能がライブラリ機能であり、これについては次の章でとり上げます。この章ではそれ以外のものを見ていく事にします。

C99言語では、ソフトウェア開発者の潜在的な興味を引きそうな、新しい機能をいくつか導入しています。こうした機能の多くはGNU C拡張セットの機能に似ていますが、残念ながら、場合によっては互換性があまり無いのです。

C++によって一般的になった機能のいくつかも採り入れられました。特にC99の標準機能として、// コメントや、宣言やコードを取り混ぜて使えるようになりました。これらはGNU Cにはずっと昔からあり、どのプラットフォームでも動作するはずです。ただし一般的には、CとC++は相変わらず別々の言語です。実際、C99はC89よりもC++との互換性が少なくなっています。いつもの事ですが、両者を取り混ぜたコードを書くのは避けるべきです。Cコードとしては良いものがC++コードとしては悪いものの場合があるのです。

C99では文字列リテラル(string literals)と識別子の両方で、Unicode文字のサポートをいくらか追加しています。ただし現実的には、システム的なサポートとして多くのユーザーが求めるようなレベルには達していません。Unicodeを使うソースに対して、他の人からもアクセスできると期待するのは尚早です。一般的に言って、ワイド文字とUnicodeに対するサポートはコンパイラーには入っているのですが、文字列処理のツールがまだ整備されていないのです。

新しい可変長配列(VLA : variable-length array)機能は部分的に利用可能です。簡単なVLAは動作しますが、これは単に偶然にすぎません。実際、GNU Cには独自の可変長配列サポートがあります。その結果、可変長配列を使った簡単なコードは動作するのですが、多くのコードは古いGNU Cでの可変長配列サポートと、C99での定義との差に突き当たってしまうのです。配列の長さがローカル変数である場合には宣言しても良いのですが、それ以上はすべきではありません。

複合リテラルと指示付きの初期化子はコードの維持管理をし易くする機能として素晴らしいものです。次の2つのコード断片を比較してみてください。

リスト1. C89でnマイクロ秒遅延させる
    /* C89 */
    {
        struct timeval tv = { 0, n };
        select(0, 0, 0, 0, &tv);
    }
リスト2. C99でnマイクロ秒遅延させる
    // C99
    select(0, 0, 0, 0, & (struct timeval) { .tv_usec = n });

複合リテラルの構文では、カッコで囲まれた一連の値を使って、適切な型の自動オブジェクトが初期化できるようになります。そのオブジェクトはその宣言が到達する毎に再度初期化されるので、(あるバージョンの select のように)対応するオブジェクトを変更するような機能に対しても安全です。指示付きの初期化子構文では、メンバーを(オブジェクトの中で現れる順番によらず)名前によって初期化できるようになります。これは、初期化するメンバーを少ししか持たない、大きく複雑なオブジェクトで特に有用です。通常の集合型初期化子の場合と同じく、値が入っていない場合には初期化子としてゼロ(0)が与えられたものとして扱われます。他の初期化規則も少し変更されています。例えば、コード生成(code generators)を少し書きやすくするために、 enum 宣言の最終メンバーの後にコンマを持つ事が許されるようになりました。

(例えば long long のような)Cの型システムの拡張については長年議論されてきました。C99では数個の新しい整数型を導入しています。最も広く使われているのは long long です。もう一つ、 intmax_t も導入しています。どちらもgccで使えます。ただし、整数拡張規則(integer promotion rules)はlongよりも大きな型に対しては必ずしも正しいとは限りません。おそらく明示的なキャストを使うのが最善でしょう。

望みの性質をより具体的に記述できるような型もたくさんあります。例えば、少なくとも8ビットを持つことを表す int_least8_t のような名前を持つ型もありますし、ぴったり32ビットを持つことを表す int32_t という名前を持つ型もあります。この標準では、少なくとも8、16、32、64ビット型へのアクセスを保証しています。ぴったりの幅を持つ型が用意されるという保証はありません。どうしても、絶対にその幅よりも大きな型が受け入れ難いのでない限り、そうした型を使うべきではありません。オプション型のもう一つは新しい intptr_t 型で、これはポインターを保持するだけの十分な大きさを持った整数です。全てのシステムでそうした型が使えるわけではありません(ただし、現在のLinuxとBSD実装ではどれでも使えます)。

Cプロセッサーにはいくつか新しい機能があります。空の引数が許されるようになり、可変数の引数を持つマクロがサポートされました。マクロを生成するpragmasに対して _Pragma 演算子があり、常に現在の機能の名前を含む __func__ マクロがあります。こうした機能は現在のバージョンのgccに用意されています。

C99は機能のインライン化を示す inline キーワードを追加しました。GNU Cでもこのキーワードをサポートしていますが、意味体系は少し異なります。もし今gccを使っていて、そのコードに対する振る舞いをC99での振る舞いと同じにしたいのであれば、インライン機能には常に static キーワードを使うべきです。これは将来の改訂では対応されるかも知れませんが、当面は inline をコンパイラーのヒントとしては使えますが、その意味通りを期待すべきではありません。

C99では restrict という修飾子を導入しました。これはコンパイラーにポインターに関する最適化のヒントを与えるものです。これでコンパイラーが何かをする必要があるという要求は無いので、gccがこれを受けつければ、それで終わりです。実際に行われる最適化は様々です。これを使っても安全ですが、大きな差があると期待すべきではありません。これに関連して、gccでは新しい、型の別名化規則が完全にサポートされています。これは大体において、型のパンニング(type punning)にもっと注意せよ、という意味になります。型パンニングは、データをアクセスする時に使用する型が unsigner char である場合をのぞいた不適切な場合、定義されない振る舞いを起こしてしまうのです。

機能引数としての配列宣言子(array declarators)は、今度はポインター宣言子とは意味の差があることになりました。型修飾子(type qualifiers)に置けるようになったのです。特に興味を引くのは、配列宣言子に static 型修飾子を与えるという、非常におかしな最適化ヒントです。この宣言を見てください。 int foo(int a[static 10]);

ポインターが少なくとも10個の int 型のオブジェクトを示さないと、そのポインターで foo() を呼ぶのは、定義されない振る舞いです。これは最適化のヒントなのです。ここでは単に、この機能に渡された引数は少なくともそれくらい大きい、ということをコンパイラーに約束しているだけなのです。マシンによってはこれをループのアンロール(loop unrolling)に使うかも知れません。古くからの人は知っているかも知れませんが、これは static キーワードに全く新しい意味が無いので、新しいC標準ではないのです。

最後に言っておきたい機能として、可変長配列(flexible array members)があります。基本的にヘッダーで、それにいくつかデータ・バイトが続いた構造体を宣言したい、というのはよくある問題です。残念ながら構造体に対して、別に割り当てた領域へのポインターを与えずにこれを行うためのうまい方法はC89にはありませんでした。よく行われる2つの解決方法は、1バイトの記憶域を持つメンバーを宣言してから余分を割り当てて配列の境界を乗り越える方法と、必要よりも大きな記憶域を持つメンバーを宣言し、使える記憶域のみを使うように注意するというものでした。どちらも一部のコンパイラーによっては問題があったので、C99ではこのための新しい構文を導入したのです。

リスト3. 可変長配列を持つ構造体
    struct header {
        size_t len;
        unsigned char data[];
    };

この構造は便利なプロパティを持っていて、 (sizeof(struct header) + 10) バイトの空間を割り当てると、データを10バイトの配列として扱えるのです。この新しい構文はgccでサポートされています。


ライブラリ機能

コンパイラーに関してはそれで良いでしょう。では標準ライブラリはどうなのでしょうか? C99で追加されたライブラリ機能の多くは既に行われている習慣、特にBSDやLinuxのコミュニティで行われている習慣に基づいています。ですからこうした機能の多くはLinuxやBSDの標準ライブラリの中に既に入っているものなのです。こうした機能の多くは単純なユーティリティ関数です。ほとんどどれも移植性のあるコードでも記述できますが、多くは非常に面倒なのです。

C99で追加された機能のうち、一番便利なものは printf 機能ファミリーの中にあります。まず、 v*scanf 機能が標準となりました。 scanf ファミリーのどのメンバーにも、対応する v*scanf 機能があり、可変引数リストの代わりに va_list パラメーターを持っています。こうした機能は v*printf 機能と同じ役割を果たし、それによって可変引数リストを持ったユーザー定義の機能が使えるようになり、 printfscanf ファミリーから機能を呼べば面倒な仕事をさせられるようになるのです。

次に、4.4BSD snprintf 機能ファミリーがインポートされました。 snprintf 機能を使うと、固定サイズのバッファーの中に安全に出力できるようになります。 snprintfn バイト以内で出力するように命令されると、文字列の最後にヌル終止符(null terminator)をつけた n-1 以下の長さの文字列生成を保証します。ところがその戻りコードは、 n が十分大きかった場合にsnprintfが書いたであろう文字数なのです。ですから何かを完全にフォーマットするために、どのくらいのバッファー空間が必要になるかが確実に分かるのです。この機能はどこにでもあり、ほとんどいつでも使うべきです。セキュリティ・ホールの多くは sprintf でのバッファー・オーバーランが元になっていますが、この機能で防ぐ事ができるのです。

特定の浮動小数点チップ用にコンパイラーを最適化できる複雑な演算機能や特殊機能を含めて、新しい標準には新しい演算機能がいくつか含まれているのですが、どこでも信頼性を持って実装できるというわけではありません。こうした機能が必要な場合には、対象としているプラットフォームそのものの上でチェックした方が確実です。浮動少数点環境機能は必ずしもサポートされているわけではなく、一部のプラットフォームではIEEE演算をサポートしません。こうした新しい機能はあまり当てにしない方が無難です。

C99では strftime() 機能が拡張され、一般的に必要とされる書式制御文字がさらにいくつか用意されています。こうした制御文字は最近のLinuxやBSDシステムでは使えるようですが、少し古いシステムでは必ずしも広く使われているというわけではありません。新しい書式を使う前には資料をよく確かめて下さい。

先に注意した通り、国際化対応コードの多くはまだ信頼性をもって実装する事はできません。

他の、新しいライブラリ機能は一般的に言って、どこででも使えるわけではありません。演算機能はスーパーコンピューターのコンパイラーには恐らく入っており、国際化対応機能はアメリカ以外で開発されるコンパイラーには入っている可能性があります。コンパイラーのベンダーはユーザーからの要求が強い機能を実装するものです。


まとめ

一般的に、新しい機能を採用するには慎重にした方が賢明です。ただ、C99の機能の多くは既に十分広範囲で使われているので、新しい開発プロジェクトでもほぼ問題なく利用する事ができます。gccコンパイラー・スイートは十分広範囲で入手可能であり、大部分のプロジェクトでは、広範囲の対象プラットフォームで採用可能と想定するのは妥当と言えます。主に対象としているのがLinux、BSDシステムのいずれか、または両方であれば、数多くある新しいC99機能のうち少なくともその一部はサポートされていると期待する事ができます。こうした機能は多くの人が認めた必要性や現実世界での実装経験に基づいて採用されているので、実際の役に立つはずです。

どの機能を使うべきか決める際には、自分が使っているコンピューターで使えるものだけを考えてはいけません。対象とするシステムをよく考えて下さい。もっと新しいディストリビューションにアップグレードするようにユーザーに要求するのですか? 対象とするマーケットでは新しいコンパイラーの入手をためらいませんか? 本式に使うと決める前に、想定される対象システムで機能をよくテストしてみて下さい。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=226889
ArticleTitle=C99を使ったオープンソース開発
publish-date=04242004