Cメイン関数の書き方

私は、今の子供たちは、PythonとJavaScriptで彼らの狂っ「アプリケーション」を書くことを知っています。しかし、C言語を拒否するので、迅速なことはありません - それは多くのものを提供し、かつ簡潔することができます。あなたはスピードが必要な場合は、あなたの答えはC言語で、おそらくある書き込み。あなたは安定した仕事を探したり、ヌルポインタ参照をキャプチャする方法を学習したい場合は、C言語では、あなたの答えかもしれません!この記事では、私はCファイルを構築し、正常にコマンドライン引数を処理するために、C、main関数の記述方法を説明します。

I:頑固なUnixシステムプログラマ。

あなた:エディタ、Cコンパイラ、および人間を殺すために時間を持っています。

それでは始めましょう。

ボーリングが、正しいCプログラム

 

Cメイン関数の書き方

 

 

パロディーオライリーブックカバー、「嫌い他の人のコード」

通常でのmain.cというファイルに保存されているmain()関数で始まるCプログラム。

/ * main.cの* / 
int型のmain(int型ARGC、チャー*のARGV []){
}

このプログラムは、コンパイルが、何もすることはできません。

$ gccのmain.cの
$ ./a.out -o fooという-vv
$

正しいけど退屈。

主な機能はユニークです。

main()関数は、最初の実行の実現の先頭にプログラムの機能ではなく、関数の最初の実行です。最初の機能は、通常、Cランタイムライブラリによって提供される_start()であり、プログラムをコンパイルするときに、自動的にリンクします。この洞察は、オペレーティング・システムとコンパイラツールチェーンに大きく依存しているので、私はそれを言及していないふりをしました。

main()関数は、一般ARGVとARGCと呼ばれる2つのパラメータを受け取り、符号付き整数を返します。ほとんどのUnix環境ではプログラムは、成功と0(ゼロ)に戻る戻る-1(マイナス1)障害が発生します。

文字ポインタのパラメータ名パラメータ説明ARGC番号ベクタ番号パラメータベクトルargv配列

ARGVパラメータベクトルは、プログラムのコマンドライン表現のマークを呼び出すことです。上記の例では、argvが以下の文字列のリストです:

ARGV = [ "/path/to/a.out"、 "-o"、 "FOO"、 "-vv"]。

パラメータベクトルは、プログラム実行のフルパスであり、そのARGV [0]において、少なくとも最初のインデックス列を確保します。

main.cのファイル解析

私はスクラッチのmain.cから書き始めたときは、次のように、その構造は、一般的に次のとおりです。

/ * main.cの* / 
/ * 0著作権/ライセンス* /
/ *含まれている1 * /
/ * 2の定義* /
/ * 3つの外部宣言* /
/ * 4タイプの定義* /
/ *グローバル変数5宣言* /
/ * 6つの関数プロトタイプ* /
int型のmain(int型ARGC、CHAR * ARGV []){
/ *コマンドラインの解析。7 * /
}
/ * *関数宣言8 /

今、私はゼロである部品番号に加えて、これらの数字を通じて話をします。あなたは、ソースコードの著作権やライセンステキストを持っている場合は、そこに置かれています。

私は議論したくないもう一つはコメントです。

「嘘をコメントしています。」 
- シニカルが、賢いと格好良いプログラマを。

代わりにコメントを使用するのではなく、意味のある関数名や変数名を使用することをお勧めします。

本来の慣性プログラマ、一度追加されたコメントを考えると、メンテナンスの負担が倍になります。変更またはコードのリファクタリング場合は、更新やコメントを拡張する必要があります。時間が経つにつれて、コードは完全に違って見えるだろう、とコンテンツの注釈は完全に異なる説明しました。

あなたは逆に、コードが何をしているのかについて書いていない、コメントを書くことがある場合は、なぜ書かれたコードを書き留めておく必要があります。あなたは、コードを忘れてしまったとき、5年間でコメントを読むことになるでしょう何かを書きます。世界の運命はあなた次第。圧力を持っていません。

図1は、含まれてい

私が最初にすることを含むファイルである、彼らはプログラムの標準C標準ライブラリ関数と変数の多くを提供のmain.cファイルに追加しました。たくさんのことを行うためのC標準ライブラリ。USR /で含まれ、あなたは彼らが何ができるかを学ぶことができます/ヘッダファイルを参照します。

#include文字列は、それが完全に現在のファイルに引用した文献を含有し、Cプリプロセッサ(CPP)コマンドです。Cヘッダファイル名は通常、拡張子.hという名前が付けられ、そして任意の実行可能コードを含むべきではありません。それだけでマクロ定義、型定義、外部変数や関数のプロトタイプ。文字列<このheader.h>定義されたヘッダ・ファイル・システム・パスにこのheader.hという名前のファイルを見つけるのcppを伝え、それは、/ usr / includeディレクトリに通常あります。

/ * main.cの* / 
書式#include <stdio.hに>
する#include <stdlib.h>に含ま
書式#include <unistd.h>
の#include <libgen.h>
書式#include <ERRNO.H>
書式#include <string.hの>
#含める<getopt.h>
書式#include <SYS / types.h>に

これがグローバルな私のデフォルトであることが紹介され、最小のコレクションが含まれて含まれています。

STDIN FILE、標準出力、標準エラー出力とFPRINT()はmalloc()、のcalloc()とreallocの()を提供STDLIB機能の家族を提供するために提供する#include標準入出力ファイル何かがEXIT_FAILUREは、EXIT_SUCCESSlibgenは、ベース名()関数のerrno errno変数を提供提供し、外部定義されたunistd全てのmemcpy()、memsetの()設け許容できる文字列値と外部の一連のgetopt OPTARG、OPTERR、OPTINDとのgetopt()関数SYS /タイプは、のuint32_tとuint64_tをのような型定義を、ショートカット提供するSTRLEN()関数

2、の定義

/ * main.cの* / 
<...>
の#define OPTSTR "VI:O:F:H"
の#define USAGE_FMT「%S [-v] [-f hexflag] [-i INPUTFILE] [-o OUTPUTFILE] [ -h]」
の#define ERR_FOPEN_INPUT "のfopen(入力、R)"
の#define ERR_FOPEN_OUTPUT "のfopen(出力、W)"
の#define ERR_DO_THE_NEEDFUL "do_the_needful"が爆破
の#define DEFAULT_PROGNAME "ジョージ"

今はあまり意味がありませんが、私はここで定義OPTSTRそれはプログラムのコマンドラインスイッチによって提案され、説明します。OPTSTRはのgetopt()の動作にどのように影響するかを学ぶための参考のgetopt(3)マニュアルページを参照してください。

USAGE_FMTは、それが使用()関数で使用されている、のprintf()形式のフォーマット文字列を定義します。

私はまた、ファイルのこの部分での#define文字列定数をしたいと思います。必要であれば、彼らは一緒に、より簡単に正しいスペル、リユース、ニュース、国際ニュースを収集することができます。

最後に、すべて大文字のに#define名前を付けるときは、変数と関数名を区別します。必要な場合は、ちょうど彼らがライン上で資産計上されることを確認し、アンダースコアで一緒に、または区切られた単語を置くことができます。

3、外部声明

/ * main.cの* / 
<...>
はextern int型のエラー番号。
EXTERNするchar * OPTARG。
extern int型OPTERR、OPTIND。

extern文の現在の名前空間(すなわち「ファイル」)に名前コンパイル単位、およびプログラムが変数にアクセスすることを可能にします。ここでは、3つの整数の変数と文字ポインタの定義を紹介します。getopt()関数によって、いくつかの変数OPTプレフィックスは通信するバンド通信チャネルの関数として、故障の原因errnoを使用する、C標準ライブラリです。

4.定義

/ * main.cの* / 
<...>
のstruct {typedefは
int型冗長。
uint32_tフラグ。
FILEの*入力。
FILEの*出力。
} options_t。

外部宣言の後、私は、労働組合を構築したい、と列挙宣言はtypedefを。typedefの名前を付けることは伝統的な習慣です。私は名前がタイプであることを示すために_tのサフィックスを使用するようにしたいです。この例では、私は4人のoptions_tメンバーが含ま構造体を宣言します。Cは、スペースに依存しないプログラミング言語であるので、私は、フィールド名が同じ列に配置されているスペースを使用します。私はちょうどそれが見え方が好き。ポインタのために、私はそれを明確にそれがポインタであることを確認するための名前の前にアスタリスクだということを宣言します。

5、グローバル変数の宣言

/ * main.cの* / 
<...>
int型dumb_global_variable = -11;

グローバル変数は、あなたがそれらを使用するべきではありません、悪い考えです。しかし、あなたはここに声明の中で、グローバル変数を使用し、それらにデフォルト値を与えることを確認する必要があります。本当に、グローバル変数を使用しないでください。

図6に示すように、関数プロトタイプ

/ * main.cの* / 
<...>
のボイドの使用(するchar * PROGNAME、int型OPT)。
int型do_the_needful(options_t *オプション)。

関数を書くときは、ここでは、main()関数への代わりに、前後にそれらを追加する関数のプロトタイプを置きます。初期のCコンパイラでは、あなたのプログラムで使用する各シンボル(変数や関数名)は、使用前に宣言しなければならないことを意味し、単一パスの戦略を、使用しています。現代のコンパイラは、彼らがコードを生成する前に、完全なシンボルテーブルを構築し、ほとんど常にマルチパスコンパイラなので、厳密に関数のプロトタイプを必要としません。しかし、時には、あなたはとても関数のプロトタイプを作成し、そうすることを続けてください、コードコンパイラを使用するように選択することはできません。

もちろん、私は常にmain()関数は、コマンドラインからあなたの着信コンテンツを理解していない使用方法()関数が含まれ、それがこの関数を呼び出します。

7、コマンドラインの解析

/ * main.cの* / 
<...>
int型のmain(int型ARGC、チャー*のARGV []){
int型OPT。
options_tオプション= {0、0x0の、STDIN、STDOUT}。
OPTERR = 0;
(!(OPT =のgetopt(ARGC、ARGV、OPTSTR))= EOF)一方
{スイッチ(OPT)
ケース'I':
(!(options.input =用のfopen(OPTARG、 "R"))){場合
にperror(ERR_FOPEN_INPUT );
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
ブレーク。
ケース'O':
(!(options.output =のfopen(OPTARG、 "W"))){場合
にperror(ERR_FOPEN_OUTPUT)。
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
ブレーク。

ケース'F':
options.flags =(のuint32_t)strtoulを(OPTARG、NULL、16);
ブレーク;

options.verbose + = 1;
ブレーク;
ケース'H':
デフォルト:
使用(ベース名(ARGV [0])、OPT)。
/ * NOTREACHED * /
休憩。
}
(do_the_needful(&オプション)= EXIT_SUCCESS!){場合
にperror(ERR_DO_THE_NEEDFUL)。
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
戻りEXIT_SUCCESS。
}

まあ、コードがもう少し。main()関数の目的は、ユーザによって提供されるパラメータを収集することで、基本的な入力の検証を行い、収集したパラメータは、それらを使用して、関数に渡されます。この例では、デフォルト値が変数のオプションを初期化し、必要に応じて更新するためのコマンドラインオプションを解析するために使用されて宣言します。

コアmain()関数は、ARGVを横断するコマンドラインオプションとその引数(もしあれば)を探すためのgetopt()を使用してwhileループです。ファイルのOPTSTRフロントは、テンプレート駆動型のgetopt()の挙動を定義しています。任意の文字のコマンドラインオプションの値は、switch文でコマンドラインオプションの発生を検出することに応答して、プログラムを見つけるために、変数のgetopt()を受け入れることを選びます。

あなたは尋ねるかもしれません気付いた場合は、なぜoptが32 int型として宣言されていますが、8文字であることが期待されますか?ファクトのgetoptで()、それが負の値の終わりに達するARGV時間をintを返し、私はEOF(ファイル終了マーカー)の一致を使用します。charが署名されているが、私は彼らの変数関数の戻り値と一致したいと思います。

知られているコマンドラインオプションを検出すると、特定の動作が発生します。コロンの最後にパラメータを指定しOPTSTRでは、これらのオプションは引数を持つことができます。オプションパラメータがある場合、次の文字列はOPTARG外部で定義された変数によってプログラムに提供することができるargvを。私は、読み取りおよび書き込み、または整数値に文字列から変換コマンドライン引数用のファイルを開くにはOPTARG使用します。

ここでは、コードのスタイルについていくつかの重要なポイントは以下のとおりです。

  • 0に初期化OPTERRは、getoptのトリガーを禁止します?。
  • またはexit(EXIT_SUCCESS); main()の出口(EXIT_FAILURE)の途中で、。
  • / * NOTREACHED * / Iは、lintコマンドが好き。
  • 戻りEXIT_SUCCESSは、関数の最後で使用intを返し、。
  • 暗黙の型キャストを表示します。

次のようにコンパイルしたこのプログラムは、コマンドライン形式:

$ ./a.out -h 
a.outの[-v] [-f hexflag] [-i入力ファイル] [-o OUTPUTFILE] [-h]

実際には、ビルド後の使用量は()stderrにそのようなコンテンツを送信します。

8、関数の宣言

/ * main.cの* / 
<...>
のボイドの使用(CHAR * PROGNAME、int型のOPT){
fprintfの(stderrに、USAGE_FMT、PROGNAME PROGNAME:?DEFAULT_PROGNAME)。
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
int型do_the_needful(options_t *オプション){
もし{(オプション!)
はerrno = EINVAL。
EXIT_FAILUREを返します。
}
IF(!オプション- >入力||オプション- >出力){
がerrno = ENOENT。
EXIT_FAILUREを返します。
}
/ * XXX入用のもの* /やる
戻りEXIT_SUCCESSを。
}

私は最後の関数はテンプレート関数ではありません書きます。本実施形態では、関数do_the_needful()options_t構造体へのポインタを受け入れます。私は、オプションのポインタがNULLでない場合、出力のメンバーの入力と構造を検証するために進んで検証しました。テストが失敗した場合、EXIT_FAILUREを返し、従来のエラーコードへの外部グローバル変数errnoによって、私はエラーの呼び出し元ルーチン原因を伝えることができます。発信者は、エラーメッセージを発行する)(PERROR便利な機能を使用することができerrnoの値に基づいて、読みやすいです。

この関数はその入力を検証するいくつかの方法で、ほとんど常にあります。完全に多大なコストで検証した場合、その後、一度、不変と考えたデータの検証を実行してみてください。条件のfprintf()の呼び出し割り当てPROGNAME認証パラメータを使用しての使用()関数。次の用法()関数が終了しますので、私はerrnoに、あなたがプログラムの正しい名前を使用するかどうかを心配する必要はありません設定する気はありません。

ここで、私は避けたい最大の過ちは、NULLポインタ参照です。これは避けられない死に至る、私のプロセスにSYSSEGVと呼ばれる特殊な信号を送信するために、オペレーティング・システムの原因となります。ほとんどのユーザーは表示したくないSYSSEGVの崩壊によって引き起こされます。好ましくは、適切なNULLポインタ捕捉はエラーメッセージを発行し、正常にプログラムを閉じます。

一部の人々は、複数のreturn文は関数本体であることを不平を言う、彼らは何か「連続制御の流れ」などについて多くのことを話しました。正直なところ、中間機能エラーならば、それはエラー状態を返す必要があります。唯一のリターンは決して「良い考え」™であることならばネストされたステートメントの多くを書きます。

5つ以上のパラメータを受け入れるための関数を書く場合は最後に、構造にそれらを結合して、構造体へのポインタを渡すことを検討してください。これは、関数のシグネチャ単純で覚えやすいなり、あなたが後に呼び出すときではない間違って行きます。また、理由はあまり何の機能スタックを複製する必要性から、わずかに速く呼び出し機能を有効にします。実際には、関数は数百万回または数十億を起動された場合にのみ、我々はこの問題を検討します。あなたはそれが意味を成していないと思うなら、それは問題ではありません。

あなたは何のコメントを言っていませんが、待って!

do_the_needful()関数の中で、私は、それはコードを説明するのではなく、プレースホルダとして設計されたコメントの特殊なタイプを書きました:

/ * XXXニードフルものを行います* /

あなたはあなたに書き込むと、時々、あなたは今、後で書きではなくなり、停止して、いくつかの特に複雑なコードを記述する必要はありません。それは私が再び戻ってきて、自分の場所に残さものです。私はXXXプレフィックスと短いノートに何をすべきかについての説明とコメントを挿入します。私はより多くの時間を持っている場合その後、私は、ソースコードでXXXを探します。プレフィックスはちょうど中に(そのような関数名や変数など)別のコンテキストでコードのライブラリに表示されにくくなっていることを確認し、何も問題はありません。

それらを一緒に入れて

あなたがこのプログラムをコンパイルするときまあ、それはまだほとんど影響はありません。しかし、今は、Cプログラムを解析し、独自のコマンドラインを構築するための固体フレームワークを持っています。

/ * main.cの-完全なリスト* / 
書式#include <stdio.hに>
する#include <stdlib.h>に含ま
書式#include <unistd.h>
の#include <libgen.h>
書式#include <ERRNO.H>
書式#include <文字列.H>
の#include <getopt.h>
の#define OPTSTR "VI:O:F:H"
の#define USAGE_FMT「%S [-v] [-f hexflag] [-i INPUTFILE] [-o OUTPUTFILE] [-h ]」
の#define ERR_FOPEN_INPUT "のfopen(入力、R)"
の#define ERR_FOPEN_OUTPUT "のfopen(出力、W)"
の#define ERR_DO_THE_NEEDFUL "do_the_needfulは"爆破
"の#define DEFAULT_PROGNAME"ジョージ
errnoにINTのextern。
EXTERNするchar * OPTARG。
extern int型OPTERR、OPTIND。
typedefは構造体{
冗長int型。
uint32_tフラグ。
FILEの*入力。
FILEの*出力。
} options_t。
int型dumb_global_variable = -11;
空の使用(するchar * PROGNAME、int型OPT)。
int型do_the_needful(options_t *オプション)。
INTメイン(int型ARGC、チャー*のARGV []){
int型OPT。
options_tオプション= {0、0x0の、STDIN、STDOUT}。
OPTERR = 0;
(!(OPT =のgetopt(ARGC、ARGV、OPTSTR))= EOF)一方
{スイッチ(OPT)
ケース'I':
(!(options.input =用のfopen(OPTARG、 "R"))){場合
にperror(ERR_FOPEN_INPUT );
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
ブレーク。
ケース'O':
(!(options.output =のfopen(OPTARG、 "W"))){場合
にperror(ERR_FOPEN_OUTPUT)。
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
ブレーク。

ケース'F':
options.flags =(のuint32_t)strtoulを(OPTARG、NULL、16);
ブレーク;
ケース'V':
options.verbose + = 1;
ブレーク;
ケース'H':
デフォルト:
使用(ベース名(ARGV [0])、OPT)。
/ * NOTREACHED * /
休憩。
}
(do_the_needful(&オプション)= EXIT_SUCCESS!){場合
にperror(ERR_DO_THE_NEEDFUL)。
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
戻りEXIT_SUCCESS。
}
ボイド使用(チャー*のPROGNAME、int型OPT){
関数fprintf(stderrに、USAGE_FMT、PROGNAME PROGNAME:DEFAULT_PROGNAME?)。
出口(EXIT_FAILURE)。
/ * NOTREACHED * /
}
int型do_the_needful(options_t *オプション){
場合(!オプション){
エラー番号= EINVAL;
EXIT_FAILUREを返します。
}
IF(!オプション- >入力||オプション- >出力){
がerrno = ENOENT。
EXIT_FAILUREを返します。
}
/ * XXX入用のもの* /やる
戻りEXIT_SUCCESSを。
}

さて、あなたはより保守C言語を記述する準備が整いました。

おすすめ

転載: www.cnblogs.com/dtdg777/p/10994853.html