いくつかの方法について聞いたことがありますが、どれも行き詰まっていません。個人的には、C で複雑な型を避け、コンポーネントの typedef に分割しようとしています。
私は今、いわゆる「三ツ星プログラマー」のレガシー コードを保守することに直面しており、***コード[][]のいくつかを読むのに苦労しています。
複雑な C 宣言をどのように読みますか?
この記事では、C 宣言を手動で読み取りたい、または手動で読み取る必要がある場合に、それを読み取ることができる比較的単純な 7 つのルールについて説明します。
- 識別子を見つけます。これがあなたの出発点です。紙に「declare identifier as」と書きます。
- 右を見てください。何もない場合、または右括弧 ")" がある場合は、手順 4 に進みます。
現在、配列 (左括弧) または関数 (左括弧) 記述子のいずれかに配置されています。これらのシーケンスが存在する場合があり、一致しない右括弧または宣言子の終わり (セミコロンまたは初期化のための "=") で終了します。そのような記述子ごとに、左から右に読んでください。
- 空の配列 "[]" の場合は、"array of" と記述します。
- サイズのある配列の場合は、「配列サイズ」と書きます
- 関数 "()" の場合は、"関数を返す" と記述します。
一致しない括弧または宣言子の終わりのいずれか早い方で停止します。
- 開始位置に戻り、左を見てください。そこに何もない場合、または左括弧 "(" がある場合は、手順 6 に進みます。
- 現在、ポインター記述子「*」に位置付けられています。一致しない左括弧「(」または宣言子の開始のいずれかで終了するこれらのシーケンスが左にある場合があります。右から左に読み取り、各ポインター記述子に対して「pointer to」を書き込みます。一致しない括弧で停止するか、または宣言子の開始のうち、最初にある方。
- この時点で、括弧で囲まれた式または完全な宣言子が得られます。括弧で囲まれた式がある場合は、それを新しい出発点と見なし、ステップ 2 に戻ります。
- 型指定子を書き留めます。止まる。
ツールに問題がない場合は、プログラムを使用することをお勧めしますcdecl
: http://gd.tuwien.ac.at/linuxcommand.org/man_pages/cdecl1.html
私は通常、「右手時計回りの法則」と呼ばれることもある法則を使用します。こんなふうになります:
注意が必要な追加のメタルールがあります。
ここで、どこかに「行く」「移動する」とは、そこで記号を読むことを意味します。そのためのルールは次のとおりです。
*
- へのポインタ()
- 関数が戻る(int, int)
- 2 つの int を取り、返す関数int
、char
など - int
、char
、など[]
- の配列[10]
- 10 の配列したがって、たとえば次のようにint* (*xyz[10])(int*, char)
読みます。
xyz は
10 の配列
へのポインタ
int* と char を受け取って返す関数
int*
一言: cdecl
くそー、15秒も殴られた!
Cdecl (および c++decl) は、C (または C++) の型宣言をエンコードおよびデコードするためのプログラムです。
http://gd.tuwien.ac.at/linuxcommand.org/man_pages/cdecl1.html
C をやっていた頃、「cdecl」というプログラムを利用していました。cutils または cdecl パッケージの Ubuntu Linux にあるようで、おそらく他の場所で入手できます。
cdecl はコマンド ライン インターフェイスを提供するので、試してみましょう。
cdecl> explain int ***c[][]
declare c as array of array of pointer to pointer to pointer to int
もう一つの例
explain int (*IMP)(ID,SEL)
declare IMP as pointer to function (ID, SEL) returning int
ただし、本「C Deep Secrets」には、「C での宣言の解読」という名前の章全体があります。
かなり洗練されたcdeclのWebベースのバージョンもあります。
「 The Development of the C Language 」の中で興味深いセクションに出くわしました:
このような構成された型の各オブジェクトに対して、基礎となるオブジェクトに言及する方法がすでにありました。配列にインデックスを付け、関数を呼び出し、ポインターで間接演算子を使用します。類推的な推論により、名前が通常現れる式構文の宣言構文を反映した名前の宣言構文が導かれました。したがって、
int i, *pi, **ppi;
整数、整数へのポインター、整数へのポインターへのポインターを宣言します。これらの宣言の構文は、i、*pi、および **ppi が式で使用されるとすべて int 型を生成するという観察を反映しています。同様に、
int f(), *f(), (*f)();
整数を返す関数、整数へのポインターを返す関数、整数を返す関数へのポインターを宣言します。
int *api[10], (*pai)[10];
整数へのポインターの配列と、整数の配列へのポインターを宣言します。これらすべての場合において、変数の宣言は、宣言の先頭で名前が付けられた型の式での使用法に似ています。
自動化されたソリューションはcdeclです。
一般に、変数は使用方法で宣言します。たとえば、次のようにポインタpを逆参照します。
char c = * p
あなたはそれを似たような方法で宣言します:
char * p;
ヘアリー関数ポインターについても同じことが言えます。fを古き良き「intへのポインタを返す関数へのポインタ」であると宣言し、おかしなことに外部宣言を宣言しましょう。これは関数へのポインタなので、次のように始めます。
extern * f();
intへのポインタを返すので、前のどこかにあります
extern int * * f(); //XXXはまだ完全ではありません
さて、正しい結合性はどれですか?思い出せないので、かっこを使います。
extern(int *)(* f)();
使用方法を宣言します。
一般的な可読性の問題には、関数ポインターと、配列が実際にはポインターであり、多次元配列が実際には 1 次元配列 (実際にはポインターである) であるという事実が含まれます。それがいくつか役立つことを願っています。
いずれにせよ、宣言を理解している場合はいつでも、次の人が読みやすいように単純化する方法を見つけられるかもしれません。