web-dev-qa-db-ja.com

「前方宣言」とは何ですか?「typedef struct X」と「struct X」の違いは何ですか?

私はCプログラミングの初心者であり、struct型宣言とtypedef struct宣言の違いを知っています。私はstructを次のように定義すると言う答えを知りました:

typedef struct { 
    some members;
} struct_name;

次に、匿名の構造体にエイリアスを提供するようなものになります(タグ名がないため)。そのため、前方宣言には使用できません。 前方宣言の意味がわかりません。

また、次のコードについても知りたいと思いました。

typedef struct NAME { 
    some members;
} struct_alias;

NAMEstruct_aliasに違いはありますか?または、struct_aliasがNAME構造体のエイリアスであるため、両方とも等しいですか?

さらに、次のようなstruct NAME型の変数を宣言できますか?

struct_alias variable1;

および/またはような:

struct NAME variable2;

またはのような:

NAME variable3; 
47
r_goyal

struct forward宣言は、ループ構造の宣言が必要な場合に役立ちます。例:

struct a {
    struct b * b_pointer;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

struct aが宣言されたとき、struct bの仕様はまだわかりませんが、前方参照できます。

匿名構造体をtypedefすると、コンパイラはtypedefの前にその名前を使用することを許可しません。

これは違法です:

struct a {
    b * b_pointer;
    int c;
};

typedef struct {
    struct a * a_pointer;
    void * d;
} b;

// struct b was never declared or defined

ただしこれは有効です:

struct a {
    struct b * b_pointer;
    int c;
};

typedef struct b {
    struct a * a_pointer;
    void * d;
} b;

// struct b is defined and has an alias type called b

これもそうです:

typedef struct b b;
// the type b referes to a yet undefined type struct b

struct a {
    b * struct_b_pointer;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

そしてこれ(Cのみ、C++では違法):

typedef int b;

struct a {
    struct b * struct_b_pointer;
    b b_integer_type;
    int c;
};

struct b {
    struct a * a_pointer;
    void * d;
};

// struct b and b are two different types all together. Note: this is not allowed in C++
51
Sergey L.

前方宣言は、定義を行うことができない時点でコンパイラに対して行うことを定義する約束です。コンパイラーはWordを使用して、他の方法では解釈できない他の宣言を解釈できます。

一般的な例は、リンクリストのノードになるように設計されたstructです。ノードへのポインターをstructに入れる必要がありますが、コンパイラーは、前方宣言またはタグ:

// Forward declaration
struct element;
typedef struct {
    int value;
    // Use of the forward declaration
    struct element *next;
} element; // Complete definition

したがって、前方宣言には使用できません

著者のポイントは、あなたのstructタグを与えることは前方宣言と同等だということだと思います:

typedef struct element {
    int value;
    // No need for a forward declaration here
    struct element *next;
} element;
23
dasblinkenlight

前方宣言は、通常、定義が利用できないときに宣言された型を参照できるようにするために、実際の定義に先行する宣言です。もちろん、すべてが宣言未定義構造で行われるとは限りませんが、特定のコンテキストではそれを使用することができます。そのようなタイプはincompleteと呼ばれ、その使用には多くの制限があります。例えば:

struct X; // forward declaration

void f(struct X*) { }  // usage of the declared, undefined structure

// void f(struct X) { }         // ILLEGAL
// struct X x;                  // ILLEGAL
// int n =sizeof(struct X);     // ILLEGAL

// later, or somewhere else altogether
struct X { /* ... */ };

これは便利です。通常は定義が非常に大きいため、循環依存関係を解消したり、コンパイル時間を短縮したりするため、解析にはより多くのリソースが必要です。

あなたの例では、struct NAMEおよびstruct_aliasは実際に同等です。

struct_alias variable1;
struct NAME variable2;

は正しい;

NAME variable3;

cでは、structキーワードは必須ではありません。

13
Marcin Łoś

struct_aliasstruct NAMEは同じです、struct_aliasstruct NAMEのエイリアスです

これらは両方とも同じで許可されています

struct_alias variable1;  

struct NAME variable1; 

これは違法です

NAME variable3;   

前方宣言 に関するこの記事を参照してください

8
Gangadhar

他の人が以前に述べたように、C/C++の前方宣言は、実際の定義が利用できない何かの宣言です。コンパイラに「データ型ABCがあります」と伝える宣言。

これをいくつかのキー/値ストア_my_dict.h_のヘッダーであるふりをしましょう:

_...
struct my_dict_t;
struct my_dict_t* create();

char* get_value(const struct my_dict_t* dict, const char* name);
char* insert(struct my_dict_t* dict, const char* name, char* value);
void destroy(struct my_dict_t* dict);
...
_

_my_dict_t_については何も知りませんが、実際には、ストアを使用するために知る必要はありません:

_#include "my_dict.h"
...
struct my_dict_t* dict = create();
if(0 != insert(dict, "AnEntry", strdup("AValue"))) {
    ...
}
...
_

その理由は次のとおりです。データ構造へのポインターのみを使用しています。

ポインターは単なる数字であり、それらに対処するために、それらが何を指しているのかを知る必要はありません。

これは、実際にそれらにアクセスしようとした場合にのみ重要です

_struct my_dict_t* dict = create();
printf("%s\n", dict->value);  /* Impossible if only a forward decl is available */
_

したがって、関数を実装するには、_my_struct_t_の実際の定義が必要です。ソースファイル_my_dict.c_で次のようにすることができます:

_#include "my_dict.h"

struct my_dict_t {
    char* value;
    const char* name;
    struct my_dict_t* next;
}

struct my_dict_t* create() {
    return calloc(1, sizeof(struct my_dict_t));
}
_

これは、次のようないくつかの状況で便利です。

  • Sergei L.のような循環型の依存関係を解決するための説明。
  • 上記の例のようなカプセル化の場合。

したがって、残っている問題は、上記の関数を使用するときに前方宣言をまったく省略できないのはなぜですか?結局、コンパイラーはすべてのdictがポインターであることを知るだけで十分です。

ただし、コンパイラは型チェックを実行します。次のようなことをしないことを確認する必要があります

_...
int i = 12;
char* value = get_value(&i, "MyName");
...
_

_my_dict_t_がどのように見えるかを知る必要はありませんが、_&i_はget_value()が期待するポインターのタイプではないことを知る必要があります。

1
Michael Beer