構造体をCでそれほど頻繁にtypedefする必要があるのはなぜですか?
質問
下のような構造で構成されるプログラムを数多く見ました
typedef struct
{
int i;
char k;
} elem;
elem user;
なぜ頻繁に必要なのですか?特定の理由または該当する分野はありますか?
解決
Greg Hewgillが言ったように、typedefは、あちこちで struct
を書く必要がないことを意味します。これはキーストロークを節約するだけでなく、smidgenをより抽象化するため、コードをきれいにすることもできます。
好きなもの
typedef struct {
int x, y;
} Point;
Point point_new(int x, int y)
{
Point a;
a.x = x;
a.y = y;
return a;
}
" struct"を見る必要がない場合、よりクリーンになります。キーワードはどこにでもあり、実際には" Point"という名前のタイプがあるように見えます。あなたの言語で。 typedef
の後、これは私が推測するケースです。
また、あなたの例(および私の例)では struct
自体の命名を省略していますが、実際に命名することは、不透明(OPAQUE)型を提供する場合にも役立ちます。次に、ヘッダーに次のようなコードがあります:
typedef struct Point Point;
Point * point_new(int x, int y);
次に、実装ファイルで struct
定義を提供します:
struct Point
{
int x, y;
};
Point * point_new(int x, int y)
{
Point *p;
if((p = malloc(sizeof *p)) != NULL)
{
p->x = x;
p->y = y;
}
return p;
}
この後者の場合、その定義はヘッダーファイルのユーザーから隠されているため、Point by valueを返すことはできません。これは、たとえば GTK + で広く使用されている手法です。
UPDATE また、 typedef
を使用して struct
を非表示にすることは悪い考えと見なされているCプロジェクトも高く評価されています。 Linuxカーネルは、おそらく最も有名なそのようなプロジェクトです。 Linux Kernel CodingStyleドキュメントの第5章を参照してください。ライナスの怒りの言葉。 :)私のポイントは、「すべき」ということです。質問では、おそらく石で設定されていません。
他のヒント
これを何人が間違っているかは驚くべきことです。 Cで構造体をtypedefしないでください。通常、大規模なCプログラムで既に非常に汚染されているグローバル名前空間を不必要に汚染します。
また、タグ名のないtypedefされた構造体は、ヘッダーファイル間の順序関係の不必要な賦課の主な原因です。
検討:
#ifndef FOO_H
#define FOO_H 1
#define FOO_DEF (0xDEADBABE)
struct bar; /* forward declaration, defined in bar.h*/
struct foo {
struct bar *bar;
};
#endif
typedefを使用せずにこのような定義を使用すると、compilandユニットがfoo.hをインクルードして FOO_DEF
定義を取得することができます。 foo
構造体の 'bar'メンバーを逆参照しようとしない場合、" bar.h"を含める必要はありません。ファイル。
また、名前空間はタグ名とメンバー名で異なるため、次のような非常に読みやすいコードを書くことができます。
struct foo *foo;
printf("foo->bar = %p", foo->bar);
名前空間は分離されているため、structタグ名と一致する変数の命名に競合はありません。
コードを保守する必要がある場合、typedefされた構造体を削除します。
Dan Saksの古い記事( http://www.ddj.com/ cpp / 184403396?pgno = 3 ):
ネーミングに関するC言語の規則 構造体は少し風変わりですが、 彼らはかなり無害です。ただし、 同じものをC ++のクラスに拡張 ルールは、バグのための小さな亀裂を開きます クロールする。
Cでは、名前sが表示されます
struct s { ... };
はタグです。タグ名はタイプではありません 名。上記の定義を考えると、 次のような宣言
s x; /* error in C */ s *p; /* error in C */
Cのエラーです。それらを記述する必要があります as
struct s x; /* OK */ struct s *p; /* OK */
ユニオンと列挙の名前 また、タイプではなくタグです。
Cでは、タグは他のすべてのタグとは異なります 名前(関数、型、 変数、および列挙定数)。 Cコンパイラはシンボル内のタグを維持します そうでない場合、概念的にはテーブル テーブルから物理的に分離 他のすべての名前を保持します。したがって、それ Cプログラムが持つことは可能です タグと別の名前の両方 同じスコープ内の同じスペル。 たとえば、
struct s s;
は宣言する有効な宣言です タイプstructの変数s。それは 良い習慣ではありませんが、Cコンパイラ それを受け入れなければなりません。私は見たことがありません Cがこれを設計した理由の根拠 方法。私はいつもそれが 間違いですが、そこにあります。
多くのプログラマー(あなたのものも含む) 本当に)構造体の名前を考えることを好む 型名として、エイリアスを定義します typedefを使用するタグの場合。にとって 例、定義
struct s { ... }; typedef struct s S;
構造体sの代わりにSを使用できます。
のようにS x; S *p;
プログラムはSを名前として使用できません 型と変数の両方(または 関数または列挙定数):
S S; // error
これは良いことです。
構造体、共用体、または enum定義はオプションです。たくさんの プログラマーは構造体定義を折りたたみます typedefに追加し、 次のように、タグ全体で:
typedef struct { ... } S;
リンクされた記事には、 typedef
を必要としないC ++の動作が、名前の隠蔽に関する微妙な問題をどのように引き起こすかについての議論もあります。これらの問題を防ぐには、一見不要であるように見えても、C ++のクラスと構造体も typedef
することをお勧めします。 C ++では、 typedef
を使用すると、名前の非表示は潜在的な問題の隠された原因ではなく、コンパイラーから通知されるエラーになります。
typedef
を使用すると、その型の変数を宣言するたびに struct
を記述する必要がなくなります。
struct elem
{
int i;
char k;
};
elem user; // compile error!
struct elem user; // this is correct
この問題の結果を常にtypedef列挙および構造化するもう1つの理由:
enum EnumDef
{
FIRST_ITEM,
SECOND_ITEM
};
struct StructDef
{
enum EnuumDef MyEnum;
unsigned int MyVar;
} MyStruct;
構造体のEnumDefのタイプミスに注意してください(Enu u mDef)?これはエラー(または警告)なしでコンパイルされ、(C標準のリテラル解釈に応じて)正しいです。問題は、構造内に新しい(空の)列挙定義を作成しただけだということです。以前の定義EnumDefを使用していません(意図したとおり)。
typdefでは、同様のタイプのタイプミスにより、不明なタイプを使用するとコンパイラエラーが発生します。
typedef
{
FIRST_ITEM,
SECOND_ITEM
} EnumDef;
typedef struct
{
EnuumDef MyEnum; /* compiler error (unknown type) */
unsigned int MyVar;
} StructDef;
StrructDef MyStruct; /* compiler error (unknown type) */
常に構造体と列挙型をtypedefすることを推奨します。
タイピングを節約するだけでなく(しゃれは意図していません;))、より安全だからです。
Linuxカーネルコーディングスタイル第5章では、 typedef
を使用します。
" vps_t"などを使用しないでください。
構造体とポインタにtypedefを使用するのは間違いです。表示されたら
vps_t a;
ソースで、それはどういう意味ですか?
対照的に、言う場合
struct virtual_container *a;
実際に「a」を確認できますです。
多くの人はtypedefが「読みやすさを助ける」と考えています。そうではありません。次の場合にのみ有用です:
(a)完全に不透明なオブジェクト(typedefは、オブジェクトが何であるかを隠すに積極的に使用されます)。
例:" pte_t"適切なアクセサー関数を使用してのみアクセスできる不透明なオブジェクト。
注意!不透明度と「アクセサー関数」それ自体は良くありません。 pte_tなどのようなもののためにそれらを持っている理由は、そこには絶対に zero 移植可能な情報が絶対にあるからです。
(b)整数型をクリアします。抽象化は、「
」であるかどうかの混乱を避けます。または" long"。 u8 / u16 / u32は完全に素晴らしいtypedefですが、ここよりもカテゴリ(d)に適しています。
注意!繰り返しますが、これには理由が必要です。何かが「符号なしlong」である場合、実行する理由はありません
typedef unsigned long myflags_t;
ただし、特定の状況下で「unsigned int」である理由が明確にある場合他の構成では「unsigned long」となる可能性がありますので、ぜひともtypedefを使用してください。
(c)スパースを使用して、型チェックのために文字通り new 型を作成する場合。
(d)特定の例外的な状況において、標準のC99型と同一の新しい型。
目と脳が 'uint32_t'のような標準型に慣れるのに少し時間がかかるだけですが、とにかく使用に反対する人もいます。
したがって、Linux固有の「u8 / u16 / u32 / u64」型および標準の型と同一の署名された同等の型は許可されますが、独自の新しいコードでは必須ではありません。
既にいずれかのタイプのセットを使用している既存のコードを編集するときは、そのコードの既存の選択に従う必要があります。
(e)ユーザー空間で使用しても安全な型。
ユーザー空間に表示される特定の構造では、C99タイプを要求できず、上記の「u32」フォームを使用できません。したがって、ユーザースペースと共有されるすべての構造で__u32および類似のタイプを使用します。
他のケースもあるかもしれませんが、ルールは基本的にtypedefを使用しないようにする必要があります。ただし、これらのルールのいずれかに明確に一致できる場合を除きます。
一般に、ポインター、または合理的に直接アクセスできる要素を持つ構造体は、決してはtypedefであってはなりません。
長所と短所があることがわかりました。有用な情報源は、独創的な本「Expert C Programming」です。 (第3章)。簡単に言うと、Cには複数の名前空間があります:タグ、タイプ、メンバー名、および識別子。 typedef
は、型のエイリアスを導入し、タグの名前空間で見つけます。つまり、
typedef struct Tag{
...members...
}Type;
は2つのことを定義します。タグ名前空間に1つのタグ、タイプ名前空間に1つのタイプ。したがって、 Type myType
と struct Tag myTagType
の両方を実行できます。 struct Type myType
や Tag myTagType
などの宣言は違法です。さらに、次のような宣言で:
typedef Type *Type_ptr;
Typeへのポインターを定義します。宣言すると:
Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;
then var1
、 var2
、および myTagType1
はTypeへのポインターですが、 myTagType2
ではありません。
上記の本では、プログラマが構造体という単語を書くのを防ぐだけであるため、typedefing構造体はあまり役に立ちません。しかし、私は他の多くのCプログラマーと同様に異議を唱えています。 Cでポリモーフィズムを実装する場合、一部の名前が難読化されることがあるため(カーネルなどの大規模なコードベースではお勧めできません)、詳細についてはこちらをご覧ください。例:
typedef struct MyWriter_t{
MyPipe super;
MyQueue relative;
uint32_t flags;
...
}MyWriter;
できること:
void my_writer_func(MyPipe *s)
{
MyWriter *self = (MyWriter *) s;
uint32_t myFlags = self->flags;
...
}
したがって、キャストによって、内部構造( MyPipe
)によって外部メンバー( flags
)にアクセスできます。私にとっては、このような機能を実行するたびに(struct MyWriter_ *)s;
を実行するよりも、型全体をキャストする方が混乱が少ないです。これらの場合、特にコードでこの手法を頻繁に使用する場合は、簡単な参照が重要です。
最後に、 typedef
ed型の最後の側面は、マクロとは対照的に、それらを拡張できないことです。たとえば、次の場合:
#define X char[10] or
typedef char Y[10]
その後宣言できます
unsigned X x; but not
unsigned Y y;
構造体については、ストレージ指定子( volatile
および const
)には適用されないため、あまり気にしません。
typedefを使用して前方宣言が可能になるとは思わない。 struct、enum、およびunionを使用すると、依存関係(知っている)が双方向の場合に宣言を転送できます。
スタイル: C ++でのtypedefの使用は、かなり意味があります。複数のパラメータや可変パラメータを必要とするテンプレートを扱う場合、ほとんど必要になる可能性があります。 typedefは、命名をまっすぐに保つのに役立ちます。
Cプログラミング言語ではそうではありません。 typedefの使用は、ほとんどの場合、データ構造の使用を難読化する以外の目的には役立ちません。 {struct(6)、enum(4)、union(5)}のキーストローク数だけがデータ型の宣言に使用されるため、structのエイリアシングにはほとんど使用されません。そのデータ型は共用体ですか、それとも構造体ですか?単純な非typdefed宣言を使用すると、その型がすぐにわかります。
このエイリアシングtypedefがもたらすエイリアスを厳密に回避してLinuxがどのように記述されているかに注意してください。結果は、ミニマリストでクリーンなスタイルです。
基本から始めて、上に向かって進みましょう。
構造定義の例を次に示します。
struct point
{
int x, y;
};
ここで、名前 point
はオプションです。
構造は、定義中または定義後に宣言できます。
定義中の宣言
struct point
{
int x, y;
} first_point, second_point;
定義後の宣言
struct point
{
int x, y;
};
struct point first_point, second_point;
ここで、上記の最後のケースに注意してください。コードの後半でそのタイプを作成することにした場合は、 struct point
を記述してそのタイプの構造を宣言する必要があります。
typedef
を入力します。後で同じブループリントを使用してプログラムで新しい構造(構造はカスタムデータ型)を作成する場合は、定義中に typedef
を使用することをお勧めします。入力を進めます。
typedef struct point
{
int x, y;
} Points;
Points first_point, second_point;
カスタムタイプに名前を付ける際の注意事項
カスタムタイプ名の末尾に_tサフィックスを使用することを妨げるものはありませんが、POSIX標準では、標準ライブラリタイプ名を示すサフィックス_tの使用が予約されています。
構造体に付ける(オプションで)名前はタグ名と呼ばれ、前述のとおり、それ自体は型ではありません。型を取得するには、structプレフィックスが必要です。
GTK +は別として、タグ名が構造体型のtypedefと同じように使用されるかどうかはわかりません。そのため、C ++では認識され、structキーワードを省略してタグ名を型名として使用することもできます:
struct MyStruct
{
int i;
};
// The following is legal in C++:
MyStruct obj;
obj.i = 7;
typedefは、相互に依存するデータ構造のセットを提供しません。これはtypdefではできません:
struct bar;
struct foo;
struct foo {
struct bar *b;
};
struct bar {
struct foo *f;
};
もちろん、いつでも追加できます:
typedef struct foo foo_t;
typedef struct bar bar_t;
そのポイントは正確に何ですか?
A> typdefは、データ型のより意味のある同義語の作成を許可することにより、プログラムの意味と文書化を支援します。さらに、移植性の問題に対してプログラムをパラメーター化するのに役立ちます(K& R、pg147、C prog lang)。
B> 構造はタイプを定義します。 Structsを使用すると、変数のコレクションを1つの単位として扱いやすくすることができます(K& R、pg127、C prog lang。)。
C> 構造体のtypedefについては、上記のAで説明しています。
D>私にとって、構造体はカスタムタイプ、コンテナ、コレクション、ネームスペース、または複雑なタイプですが、typdefは単なるニックネームを作成する手段にすぎません。
C99 typedefを有効にする必要があります。それは時代遅れですが、多くのツール(ala HackRank)は純粋なC実装としてc99を使用しています。そしてそこにはtypedefが必要です。
要件が変更された場合、変更する必要があるとは言っていません(Cオプションが2つある可能性があります)。
「C」プログラミング言語では、キーワード「typedef」を使用して、オブジェクト(構造体、配列、関数..enum型)の新しい名前を宣言します。たとえば、「struct-s」を使用します。 「C」では、「main」関数の外側で「struct」を宣言することがよくあります。例:
struct complex{ int real_part, img_part }COMPLEX;
main(){
struct KOMPLEKS number; // number type is now a struct type
number.real_part = 3;
number.img_part = -1;
printf("Number: %d.%d i \n",number.real_part, number.img_part);
}
構造体タイプを使用するたびに、このキーワード 'struct' something '' name 'が必要になります。'typedef'は単純にそのタイプの名前を変更し、必要に応じてプログラムでその新しい名前を使用できます。したがって、コードは次のようになります。
typedef struct complex{int real_part, img_part; }COMPLEX;
//now COMPLEX is the new name for this structure and if I want to use it without
// a keyword like in the first example 'struct complex number'.
main(){
COMPLEX number; // number is now the same type as in the first example
number.real_part = 1;
number.img)part = 5;
printf("%d %d \n", number.real_part, number.img_part);
}
プログラム全体で使用されるローカルオブジェクト(構造体、配列、貴重な)がある場合、 'typedef'を使用して名前を付けることができます。
C言語では、struct / union / enumはC言語のプリプロセッサによって処理されるマクロ命令です("#include"などを処理するプリプロセッサと間違えないでください)
so:
struct a
{
int i;
};
struct b
{
struct a;
int i;
int j;
};
struct bは次のように消費されます:
struct b
{
struct a
{
int i;
};
int i;
int j;
}
そのため、コンパイル時にスタック上で次のように進化します。 b: int ai int i int j
自己参照構造を持つのが難しい理由も、Cプリプロセッサは終了できないdé宣言ループでラウンドします。
typedefは型指定子です。つまり、Cコンパイラのみがそれを処理し、アセンブラコードの実装を最適化するのと同じように実行できることを意味します。また、préプロセッサが構造体で行うように愚かにpar型のメンバーを費やしてはいけませんが、より複雑な参照構築アルゴリズムを使用するため、次のような構築です:
typedef struct a A; //anticipated declaration for member declaration
typedef struct a //Implemented declaration
{
A* b; // member declaration
}A;
は許可され、完全に機能します。また、この実装により、コンパイラの型変換へのアクセスが可能になり、実行スレッドが初期化関数のアプリケーションフィールドを離れるときにバグの影響が取り除かれます。
これは、Cではtypedefが孤独な構造体よりもC ++クラスに近いことを意味します。