web-dev-qa-db-ja.com

なぜC言語で構造体を頻繁に型定義するべきなのでしょうか。

以下のような構造からなる多くのプログラムを見ました

typedef struct 
{
    int i;
    char k;
} elem;

elem user;

なぜそれがそれほど頻繁に必要なのでしょうか。具体的な理由や該当分野はありますか?

357
Manoj Doubts

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を値で返すことはできません。これは、たとえば GTK + で広く使用されている手法です。

_ update _ このtypedefを隠すためのstructの使用はよくない考えと考えられているCプロジェクトも非常に注目されていることに注意してください。Linuxカーネルはおそらく最も有名なプロジェクトです。 Linusの怒っている言葉については、 Linux Kernel CodingStyleのドキュメント の第5章を参照してください。 :)私の論点は、質問の「べき」はおそらく結局のところ設定されていないということです。

421
unwind

多くの人々がこれを誤解しているのは驚くべきことです。 Cで構造体をtypedefしないでください、それは不必要に大規模なCプログラムで既に非常に汚染されているグローバル名前空間を不必要に汚染します。

また、タグ名なしの型定義された構造体は、ヘッダーファイル間の順序関係を不必要に課す主な原因です。

検討してください:

#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);

名前空間は別々であるため、それらの構造体タグ名と一致する変数の命名に競合はありません。

私があなたのコードを保守しなければならないならば、私はあなたのtypedefされた構造体を削除します。

198
Jerry Hicks

Dan Saksによる古い記事( http://www.ddj.com/cpp/184403396?pgno = 3 )から:


構造体の命名に関するC言語の規則は少し変わっていますが、それらはまったく無害です。ただし、C++のクラスに拡張した場合、これらの同じ規則を使用してバグをクロールすることができます。

Cでは、名前は

struct s
    {
    ...
    };

タグです。タグ名は型名ではありません。上記の定義を考えると、

s x;    /* error in C */
s *p;   /* error in C */

cのエラーです。

struct s x;     /* OK */
struct s *p;    /* OK */

共用体と列挙型の名前も型ではなくタグです。

Cでは、タグは他のすべての名前(関数、型、変数、および列挙定数の場合)とは異なります。 Cコンパイラは、概念的には他のすべての名前を保持するテーブルから物理的に分離されていない限り、シンボルテーブル内にタグを保持します。したがって、Cプログラムでは、同じスペルのタグと別の名前の両方を同じスコープ内に含めることができます。例えば、

struct s s;

struct型の変数を宣言する有効な宣言です。それは良い習慣ではないかもしれませんが、Cコンパイラはそれを受け入れなければなりません。 Cがこのように設計された理由についての理論的根拠を私は見たことがありません。私はいつもそれが間違いだと思っていましたが、それはあります。

多くのプログラマー(あなたを含む)は構造体の名前を型の名前と考えることを好むので、typedefを使ってタグのエイリアスを定義します。例えば、

struct s
    {
    ...
    };
typedef struct s S;

次のように、構造体sの代わりにSを使用できます。

S x;
S *p;

プログラムは、Sを型と変数(または関数または列挙定数)の両方の名前として使用することはできません。

S S;    // error

これはいい。

構造体、共用体、または列挙型定義内のタグ名はオプションです。次のように、多くのプログラマは構造体の定義をtypedefにまとめてタグを省略します。

typedef struct
    {
    ...
    } S;

リンクされた記事には、typedefを必要としないというC++の振る舞いが微妙な名前隠蔽の問題を引き起こす可能性があるという議論もあります。これらの問題を防ぐには、一見したところ不要なように見えますが、C++でクラスや構造体をtypedefすることをお勧めします。 C++では、typedefを使用すると、名前の隠蔽によって、潜在的な問題の隠れた情報源ではなく、コンパイラから通知されるエラーになります。

134
Michael Burr

typedefを使用すると、その型の変数を宣言するたびにstructを書く必要がなくなります。

struct elem
{
 int i;
 char k;
};
elem user; // compile error!
struct elem user; // this is correct
57
Greg Hewgill

常に列挙型と構造体を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) */

私は常に構造体と列挙型を型定義することを提唱します。

多少のタイピングを節約するだけではなく(意図せずに;))、安全だからです。

37
cschol

Linuxカーネルコーディングスタイル 第5章では、typedefを使用することの長所と短所(主に短所)を示しています。

「vps_t」などを使用しないでください。

構造体とポインターにtypedefを使用するのはmistakeです。あなたが見たとき

vps_t a;

ソースでは、それはどういう意味ですか?

対照的に、それが言うなら

struct virtual_container *a;

実際に「a」とは何かを知ることができます。

多くの人がtypedefが「読みやすさを助ける」と考えています。そうではありません。これらは次の場合にのみ有用です。

(a)完全に不透明なオブジェクト(typedefがアクティブに使用され、hideオブジェクトが何であるか).

例:「pte_t」など。適切なアクセサー関数を使用してのみアクセスできる不透明なオブジェクト。

注意!不透明度と「アクセサ機能」はそれ自体では良くありません。 pte_tなどのようなもののためにそれらを持っている理由は、そこには絶対にzeroのアクセス可能な情報が本当にあるからです。

(b)整数型をクリアします。抽象化helpsは、「int」または「long」の混乱を避けます。

u8/u16/u32は完全にすばらしいtypedefですが、ここよりもカテゴリ(d)に適しています。

注意!繰り返しますが、これにはreasonが必要です。何かが「符号なし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を絶対に使用しないようにする必要があります。ただし、これらのルールの1つに明確に一致できる場合を除きます。

一般に、ポインター、または合理的に直接アクセスできる要素を持つ構造体は、neverがtypedefである必要があります。

29
Yu Hao

長所と短所があることがわかりました。有用な情報源は、精巧な本 "Expert C Programming"( Chapter 3 )です。簡単に言うと、Cでは複数の名前空間があります: タグ、型、メンバー名、識別子 typedefは型の別名を導入し、それをタグ名前空間に配置します。すなわち

typedef struct Tag{
...members...
}Type;

2つのことを定義します。タグ名前空間内の1つのタグと型名前空間内の1つのType。そのため、Type myTypestruct Tag myTagTypeの両方を実行できます。 struct Type myTypeTag myTagTypeのような宣言は違法です。また、このような宣言では:

typedef Type *Type_ptr;

typeへのポインタを定義します。だから我々は宣言する場合:

Type_ptr var1, var2;
struct Tag *myTagType1, myTagType2;

var1var2myTagType1はTypeへのポインタですが、myTagType2はそうではありません。

上記の本では、typedefing構造体はプログラマーがWord構造体を書くのを節約するだけなのであまり有用ではないと述べています。しかし、他の多くの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;
...
}

そのため、キャストを通じて内部構造体(flags)によって外部メンバー(MyPipe)にアクセスできます。私にとっては、そのような機能を実行するたびに(struct MyWriter_ *) s;を実行するよりも、型全体をキャストする方が混乱が少ないです。このような場合、特にコード内でこの手法を頻繁に使用する場合は、簡単な参照が重要です。

最後に、typedefed型の最後の側面は、マクロとは対照的に、それらを拡張できないことです。たとえば、次のようになります。

#define X char[10] or
typedef char Y[10]

あなたはそれから宣言することができます

unsigned X x; but not
unsigned Y y;

構造体はストレージ指定子(volatileconst)には適用されないため、構造体についてはこれを気にしません。

10
user1533288

前方宣言はtypedefでも可能だとは思わない。構造体、列挙型、および共用体を使用すると、依存関係(知っている)が双方向である場合に宣言を転送することができます。

スタイル:C++でtypedefを使用すると、かなり意味があります。複数のパラメータや可変のパラメータ、あるいはその両方を必要とするテンプレートを扱う場合は、ほとんど必要になります。 typedefは命名を正確に保つのに役立ちます。

Cプログラミング言語ではそうではありません。 typedefを使用しても、ほとんどの場合、データ構造の使用法を難読化する以外の目的はありません。データ型を宣言するために使用されるのは{struct(6)、enum(4)、union(5)}個のキーストロークだけなので、構造体のエイリアスにはほとんど意味がありません。そのデータ型は共用体ですか、それとも構造体ですか。直接的な型指定されていない宣言を使用すると、それがどんな型であるかをすぐに知ることができます。

Linuxがどのように書かれているかに注目してください。このエイリアスのナンセンスtypedefがもたらす厳密な回避のもとで。その結果、シンプルで清潔なスタイルになりました。

10
natersoz

基本から始めて、私たちの方法で作業を進めましょう。

これが構造体定義の例です。

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を使用することを予約しています。

4
Asif

あなたが(オプションで)構造体に付ける名前は タグ名 と呼ばれ、すでに述べたようにそれ自体は型ではありません。型に到達するには構造体接頭辞が必要です。

GTK +は別にして、私はtagnameがstruct型のtypedefのように一般的に使われているかどうかわからないので、C++ではそれが認識されています。


    struct MyStruct
    {
      int i;
    };

    // The following is legal in C++:
    MyStruct obj;
    obj.i = 7;
3
philsquared

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;

そのポイントは何ですか?

1
natersoz

A typdefは データ型のためのより意味のある同義語の作成を許可することによってプログラムの意味と文書化を助けます 。さらに、それらは移植性の問題に対してプログラムをパラメータ化するのを助けます(K&R、pg147、Cプログラム)。

B> 構造体が型を定義する 。 Structsでは、取り扱いの便宜のためにvarのコレクションを簡単にグループ化することができます(K&R、pg127、Cプログラム)。

C>構造体を型定義することは上のAで説明されています。

D>私にとっては、構造体はカスタム型、コンテナ、コレクション、ネームスペース、または複合型ですが、typdefは単なるより多くのニックネームを作成するための手段です。

1
JamesAD-0

'C'プログラミング言語では、キーワード 'typedef'はあるオブジェクト(struct、array、function..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'を使って名前を付けることができます。

0
RichardGeerify

C99で判明typedefが必要です。時代遅れですが、多くのツール(ala HackRank)は純粋なCの実装としてc99を使用しています。そしてtypedefが必要です。

要件が変更された場合、それらが変更されるべきであると私は言っていません、私たちのサイトでのインタビューを詐欺するのはSOLだろう。

0