なんで?
私たちの周りの誰もが常に言います:「あなたはプロのプログラムを書く方法を学びたいですか? 他の人がそれをどのように行うかを見てください!」 それで、特に大学の研究が終わりに近づいているので、このアドバイスに従うことにしました。 彼らが教えた方法と実際の世界でそれを行う方法を比較することは特に興味深い。 GNU Coreutilsパッケージは、従う例として選ばれました。 それにはすべてがあります:
- 厳しい移植性の要件。
- 大きなライフサイクル。
- 開発者の巨大なチーム。
- さまざまな複雑さのコード:些細なエコーから超洗練されたsed、純粋に適用されたwcからmkdir OSに近いものまで。
GNU Coreutils
GNU Core Utilitesは、基本的なユーザー操作を実行するための一連のユーティリティです:ディレクトリの作成、画面へのファイルの表示など。 開発者によると、これらのユーティリティはすべてのオペレーティングシステムで利用可能である必要があります。現在観察しているのは、Windows用のCygwinがありますが、* nixについては何も言えません。 Coreutilsが準拠しようとする POSIX標準は、異なるシステム間で作業の均一性を維持するのに役立ちます。 Coreutilsには、cat、tail、echo、wcなどの一般的に使用されるユーティリティが含まれています。
最初に、yesと呼ばれる最も単純なプログラムを選択しましょう。 そのシンプルさにより、Coreutilsで使用されるツールとライブラリを理解できます。
ユーティリティはい
マナが言うように、yesユーティリティでできることは、「yn」をstdoutに無限に出力することだけです。 yesに引数を渡すと、「y」の代わりにyesが引数をスペース付きで出力します。 確かに、同様のプログラムがCを勉強し始めたすべての人によって書かれました。つまり、多くの人が、GNUの厳しいひげを生やしたおじさんのやり方と彼らのアプローチを比較する機会を持っています。 実用的なアプリケーションについてはいはウィキペディアに少し書かれています。
ソースコード
ソースコードに渡します。
apt-get source
を使用して取得し、デフォルトでシステムで使用されているバージョンを取得するか、リポジトリから最新バージョンを取得します。 2番目のオプションを選択します。より便利で使いやすいです。
- Coreutils:
git clone git://git.sv.gnu.org/coreutils
- Gnulib(数回そこを見てください):
git clone git://git.savannah.gnu.org/gnulib.git
ソースコードは、はい1つの
coreutils/src/yes.c
に
coreutils/src/yes.c
、それを開きます。
コーディングスタイル
最初に注意することは、コードの異常なフォーマットです。 これについては、 対応する GNUコーディング標準の章で読むことができます。 たとえば、関数を定義する場合、戻り値の型は左角括弧のように別の行にある必要があります。
int main (int argc, char **argv) { foo(); ... }
インデントと配置にはスペースのみが使用されます。 ネストのレベルが異なる場合、インデントの違いは2スペースです。 演算子を使用した中括弧は、特にひねくれた形をしています。
if (x < foo (y, z)) haha = bar[4] + 5; else { while (z) { haha += foo (z, z); z--; } return ++x + bar (); }
12行
yes.c
は、すべてのGPLプログラムの必須コメントで始まります。 彼はすでに他のプログラムで私の目のために祈っていたので、彼の存在の必要性は私にとって謎でした。 このコメントのテキストは、GPLの使用説明書で修正されていることがわかりました。 GPLの下でソフトウェアをリリースしたい人は誰でも、これらの12行のコピーライトステートメントを各ソースコードファイルの先頭に追加する必要があると書かれています。
initialize_main
プログラムが最初に行うことは、
initialize_main
呼び出すことです。 この関数は、プログラムが引数に対して特定のアクションを実行するためのものです。 実際には、Coreutilsには、この機能を有用なものに使用する単一のユーティリティはありません。
coreutils/src/system.h
あるスタブが使用される
coreutils/src/system.h
:
#ifndef initialize_main # define initialize_main(ac, av) #endif
プログラム名
Coreutilsユーティリティは、2つのプログラム名を区別します。
- ユーザーが変更できない正式名。
- 実行可能ファイルの実際の名前。
公式名は、アプリケーションのバージョン情報を表示するために使用されます。
user@laptop:~$ yes --version yes (GNU coreutils) 8.5 Usage: yes [STRING]... or: yes OPTION
さらに、この名前は実行可能ファイルの名前に依存しません。
user@laptop:~$ /usr/bin/yes --version yes (GNU coreutils) 8.5 user@laptop:~$ cp /usr/bin/yes ./foo user@laptop:~$ ./foo --version yes (GNU coreutils) 8.5
この動作は、ファイルの先頭で特別に定義された
PROGRAM_NAME
マクロによって保証されます。
/* The official name of this program (eg, no `g' prefix). */ #define PROGRAM_NAME "yes"
本名は
argv[0]
からトリックなしで取得され、エラーとヒントを表示するために使用されます。
user@laptop:~$ yes --help Usage: yes [STRING]... or: yes OPTION user@laptop:~$ /usr/bin/yes --help Usage: /usr/bin/yes [STRING]... or: /usr/bin/yes OPTION
set_program_name
の2行目の
set_program_name
関数を呼び出すことにより、
argv[0]
の値がグローバル変数
program_name
配置されます。
set_program_name (argv[0]);
set_program_name
関数
set_program_name
、 Gnulibライブラリによって提供されます。 対応するコードは、
gnulib/lib/
ディレクトリの
progname.h
および
progname.c
。
set_program_name
は、
argv[0]
値を
progname.h
で宣言されたグローバル変数
program_name
保存するだけでなく、動的ライブラリを開発するためのツールであるGNU Libtoolの使用に関連する追加の変換も実行することに注意してください。
国際化
Coreutilsは世界中で使用されているため、すべてのユーティリティにローカライズする機能があります。 さらに、この機能は、 GNU gettextパッケージの使用により最小限の労力で提供されます。 このパッケージはGNUプロジェクトをはるかに超えて広がっているため、gettextの使用は少し驚くべきことです。 たとえば、私のお気に入りのDjango Webフレームワークの国際化は、 特にgettextに基づいて構築されています。 さまざまな言語やフレームワークと一緒にgettextを使用することについては、すでにHabrで書いています 。
gettextの優れた特性は、すべての言語でほぼ同じ方法で使用されることであり、Cも例外ではありません。 標準のマジック関数
_
、その使用
usage
は
usage
関数にあります:
void usage (int status) { if (status != EXIT_SUCCESS) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); ... }
_
関数の定義は、使い慣れた
system.h
ファイルにあります。
#define _(msgid) gettext (msgid)
Coreutilsの国際化メカニズムの初期化は、
main
3つの関数を呼び出すことにより行われます。
setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE);
- setlocaleは、アプリケーションで動作するようにデフォルトのロケール環境を設定します
- bindtextdomainは、特定のメッセージドメインの翻訳ファイルを探す場所を指定します
- textdomainは現在のメッセージドメインを設定します
エラー処理
main
コードに沿ってさらに進むと、次の行があります。
atexit (close_stdout);
直観的に、
close_stdout
関数は標準出力ストリームを閉じ、
stdout
を何らかのファイル記述子に置き換えてバッファリングされた出力を使用する場合、データ損失を排除すると考えるかもしれません。 しかし、リソースをクリーンアップするために追加のアクションが実行されるかどうかにかかわらず、この関数のソースコードを見つけて実際にそこで何が起こるかを理解することに成功しませんでした。
コマンドライン引数
これは、プログラム自体の作業に関係しない最後の質問です。 ここでは、国際化の場合と同様に、多くのプロジェクトにクロールされた実績のあるソリューション( Pythonなど ) -getoptモジュールを使用します。 このモジュールは非常に単純です。実際、開発者はループ内で
getopt
または
getopt_long
関数のいずれかを呼び出す必要があります。 インターネットでgetoptの詳細を読むことができます。また、ハブについても書いています。
Gnulibには
parse_long_options
特別な関数があり、引数
--version
および
--help
を処理します。これは、GNUアプリケーションがサポートする必要があります。 これは
gnulib/lib/long-options.c
ファイルにあり、作業で
getopt_long
を使用します。
yesのソースコードは、getoptのクールな例です。 同時に、多数の引数を解析することで学習に不必要な複雑さはなく、すべてのgetoptツールの使用があります。 最初に、もちろん、
parse_long_options
呼び出しが
parse_long_options
ます。 次に、これ以上キーオプションが渡されず、残りの引数があれば、それが単なる任意の文字列であることを確認します。
parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, usage, AUTHORS, (char const *) NULL); if (getopt_long (argc, argv, "+", NULL, NULL) != -1) usage (EXIT_FAILURE);
次のコードは、次のようにロシア語に翻訳できます。「コマンドラインの引数リストに--versionおよび--helpキー以外に何もなかった場合、「y」をstdoutに出力します」:
if (argc <= optind) { optind = argc; argv[argc++] = bad_cast ("y"); }
argv[argc]
への書き込みはエラーで
argv[argc]
ません
argv[argc]
標準では、
argv[argc]
要素がNULLポインターであることを要求しています。
メインサイクル
さて、私たちはプログラムのまさに機能性に到達しました。 そのままです:
while (true) { int i; for (i = optind; i < argc; i++) if (fputs (argv[i], stdout) == EOF || putchar (i == argc - 1 ? '\n' : ' ') == EOF) error (EXIT_FAILURE, errno, _("standard output")); }
ここで、すべてのアクションは本体ではなく
if
条件内で実行されることに注意してください。 そのため、KerniganとRitchieは、経験豊富なCプログラマーが次のような行のコピーを実装していると書いたときには嘘をつきませんでした。
while (*dst++ = *src++) ;