次のヘッダーファイルに1つまたは2つのタイプがあるかどうかを示す、3つのC標準(できればC99またはC11)の1つから章と節を与えることができますstruct uperms_entry
か?
#ifndef UPERMS_CACHE_INCLUDE
#define UPERMS_CACHE_INCLUDE
typedef struct mutex MT_MUTEX;
typedef struct uperms_cache
{
MT_MUTEX *cache_lock;
int processing;
struct uperms_entry *uperms_list; // No prior struct uperms_entry
} uperms_cache_t;
typedef struct uperms_entry // Does this define a different struct uperms_entry?
{
char username[32];
int perms;
struct uperms_entry *next;
} uperms_entry_t;
#endif /* UPERMS_CACHE_INCLUDE */
補助的な質問:
- 2つのタイプがある場合、GCCに問題を報告させる方法はありますか?
- 2つのタイプがある場合、それは実際に重要ですか?
(答えは「はい—厳密には2つのタイプがあります」、そして(1)いいえと(2)いいえです。)
コンテキスト:内部コードレビュー—構造の順序を逆にしたいのですが、完全に過度に衒学的であるかどうかはわかりません。
アップデート:
明らかに、最初の質問に対する答えは「1つstruct uperms_entry
あります」であるため、1と2の番号が付けられた質問は議論の余地があります。コードレビューでヒッシーフィットを投げる前にチェックしてよかったです。
背景思考
このセクションは、主要な質問が解決されてからずっと後に追加されました。
以下は、ISO / IEC 9899:2011からの広範囲で関連性のある引用です。
§6.2.7互換型と複合型
¶12つのタイプは、タイプが同じである場合、互換性のあるタイプになります。2つの型が互換性があるかどうかを判断するための追加の規則は、型指定子については6.7.2、型修飾子については6.7.3、宣言子については6.7.6で説明されています。55)さらに、タグとメンバーが次の要件を満たしている場合、別々の変換単位で宣言された2つの構造体、共用体、または列挙型は互換性があります。一方がタグで宣言されている場合、もう一方は同じタグで宣言されます。両方がそれぞれの翻訳単位内のどこかで完了する場合、次の追加要件が適用されます。対応するメンバーの各ペアが互換性のあるタイプで宣言されるように、メンバー間に1対1の対応が必要です。ペアの一方のメンバーがアライメント指定子で宣言されている場合、もう一方のメンバーは同等のアライメント指定子で宣言されます。ペアの一方のメンバーが名前で宣言されている場合、もう一方のメンバーは同じ名前で宣言されます。2つの構造の場合、対応するメンバーは同じ順序で宣言されるものとします。2つの構造体またはユニオンの場合、対応するビットフィールドの幅は同じでなければなりません。2つの列挙の場合、対応するメンバーは同じ値を持つ必要があります。
55)互換性を保つために、2つのタイプが同一である必要はありません。
§6.7.2.1構造とユニオンの指定子
¶8struct-or-union-specifierにstruct-declaration-listが存在すると、変換ユニット内で新しい型が宣言されます。struct-declaration-listは、構造体または共用体のメンバーの一連の宣言です。struct-declaration-listに、直接または匿名構造体または匿名ユニオンを介して名前付きメンバーが含まれていない場合、動作は未定義です。
}
タイプは、リストを終了する直後まで不完全であり、その後は完了します。§6.7.2.3タグ
¶4同じスコープを持ち、同じタグを使用する構造体、共用体、または列挙型のすべての宣言は、同じ型を宣言します。タグがあるかどうか、またはタイプの他の宣言が同じ翻訳単位にあるかどうかに関係なく、コンテンツを定義するリストの閉じ中括弧の直後まで、タイプは不完全です129) 、その後完了します。
¶5異なるスコープにあるか、異なるタグを使用する構造、共用体、または列挙型の2つの宣言は、異なる型を宣言します。タグを含まない構造体、共用体、または列挙型の各宣言は、個別の型を宣言します。
¶6形式の型指定子
struct-or-union identifier
オプト{ struct-declaration-list }
また
enum identifier
オプト{ enumerator-list }
また
enum identifier
オプト{ enumerator-list , }
構造体、共用体、または列挙型を宣言します。このリストは、構造体コンテンツ、共用体コンテンツ、または列挙コンテンツを定義します。識別子が提供される場合、130)型指定子は、識別子がその型のタグであることも宣言します。
¶7フォームの宣言
struct-or-union identifier ;
構造体または共用体の型を指定し、その型のタグとして識別子を宣言します。131)
¶8フォームの型指定子の場合
struct-or-union identifier
上記のフォームの一部として以外に発生し、タグとしての識別子の他の宣言が表示されない場合は、不完全な構造体または共用体型を宣言し、その型のタグとして識別子を宣言します。131)
¶9フォームの型指定子の場合
struct-or-union identifier
また
enum identifier
上記のいずれかの形式の一部として以外に発生し、タグとしての識別子の宣言が表示されている場合、他の宣言と同じタイプを指定し、タグを再宣言しません。
¶12例2相互参照構造のペアを指定するためのタグの事前宣言の使用を説明するために、宣言
struct s1 { struct s2 *s2p; /* ... */ }; // D1 struct s2 { struct s1 *s1p; /* ... */ }; // D2
相互へのポインターを含む構造体のペアを指定します。ただし、s2が囲んでいるスコープでタグとしてすでに宣言されている場合、宣言D1は、D2で宣言されたタグs2ではなく、それを参照することに注意してください。このコンテキストの機密性を排除するために、宣言
struct s2;
D1の前に挿入できます。これにより、内部スコープで新しいタグs2が宣言されます。次に、宣言D2は新しいタイプの指定を完了します。
129)不完全な型は、その型のオブジェクトのサイズが必要ない場合にのみ使用できます。たとえば、typedef名が構造体またはユニオンの指定子として宣言されている場合、または構造体またはユニオンを返すポインターまたは関数が宣言されている場合は必要ありません。(6.2.5の不完全な型を参照してください。)このような関数を呼び出すか定義する前に、仕様を完成させる必要があります。
130)識別子がない場合、タイプは、翻訳単位内で、それが一部である宣言によってのみ参照できます。もちろん、宣言がtypedef名の場合、後続の宣言はそのtypedef名を使用して、指定された構造、共用体、または列挙型を持つオブジェクトを宣言できます。
131)列挙型の同様の構造は存在しません。
§6.7.3型修飾子
¶102つの修飾型が互換性を持つためには、両方が互換性のある型の同じ修飾バージョンを持っている必要があります。指定子または修飾子のリスト内の型修飾子の順序は、指定された型に影響しません。
§6.7.6の説明は、ポインター、配列、および関数宣言子に関連しており、構造体や共用体には実際には影響しません。
質問を書いたとき、私は例2を知っていました。これは、上記の情報が何を意味するかについて大声で考えている人です。
きれいにコンパイルされるこの例を考えてみましょう。
#include <stdio.h>
struct r1 { int x; };
struct r1;
struct r1 p0;
//struct r1 { int y; }; // Redefinition of struct r1
extern void z(void);
void z(void)
{
struct r1 p1 = { 23 };
struct r1;
//struct r1 p2; // Storage size of p2 is not known
struct r2 { struct r1 *rn; int y; };
struct r1 { struct r2 *rn; int z; };
struct r2 p = { 0, 1 };
struct r1 q = { &p, 2 };
p.rn = &q;
printf("p.y = %d, q.z = %d\n", p.y, q.z);
printf("p1.x = %d\n", p1.x);
}
この関数は、例2がいつ適用されるかを示していますが、適切なコードではありません。関数内のの宣言はp1
、グローバル変数と同じ型の構造体になりますp0
。タイプ名はですstruct r1
が、ローカル変数のタイプとは異なる(互換性のない)タイプですp
。
要素の名前がであるか。struct r1
であるかに関係なく、グローバルレベルでの再定義は許可されていません。このコンテキストでは、事前設定
はノーオペレーションです。x
y
struct r1;
興味深い問題の1つは、'関数を他の関数にz
渡すことができるp
か(それを呼び出す)?答えは修飾された「はい」であり、いくつかの制約は興味深いものです。(それを試してみるのも恐ろしいコーディングスタイルであり、非常識なことになります。)関数は別の変換ユニット(TU)に存在する必要があります。関数宣言は関数の内部にある必要があります(関数の外部にある場合、そのプロトタイプは、定義された内部ではなく、関数の外部で定義されたものを参照する必要があるためです。q
a
z
struct r1
struct
r1
他のTUでは、ある程度の健全性が優先される必要があります。関数a
は、互換性のある構造タイプstruct r1
を持ちstruct r2
、グローバルスコープで表示される必要があります。
別の例を示しますが、これはコンパイルされません。
#include <stdio.h>
struct r1;
extern void z(struct r1 *r1p);
extern void y(struct r1 *r1p);
void y(struct r1 *r1p)
{
struct r2 { struct r1 *rn; int y; };
struct r1 { struct r2 *rn; int z; };
struct r2 p = { r1p, 1 };
struct r1 q = { &p, 2 };
p.rn = &q;
printf("p.y = %d, q.z = %d\n", p.y, q.z);
}
void z(struct r1 *r1p)
{
struct r1
struct r2 { struct r1 *rn; int y; };
struct r1 { struct r2 *rn; int z; };
struct r2 p = { r1p, 1 };
struct r1 q = { &p, 2 };
p.rn = &q;
printf("p.y = %d, q.z = %d\n", p.y, q.z);
}
Mac OSX10.7.4上のGCC4.7.1からの警告は次のとおりです。
structs3.c: In function 'y':
structs3.c:13:10: warning: assignment from incompatible pointer type [enabled by default]
structs3.c: In function 'z':
structs3.c:22:12: warning: initialization from incompatible pointer type [enabled by default]
structs3.c:22:12: warning: (near initialization for 'p.rn') [enabled by default]
p.rn = &q;
13行目は関数での割り当てであり、23行目は関数y
での定義と初期化の試みです。struct r2 p
z
これは、関数内で、のrn
要素がグローバルスコープで宣言されstruct
r2
た不完全な型へのポインターであることを示しています。struct r1
関数内のコードの最初の行としてaを追加するstruct r1;
と、コードをコンパイルできますが、初期化参照r1p->rn
は、不完全な型へのポインターの参照を再度解除します(不完全な型はstruct r1
グローバルスコープで宣言されます)。
関数宣言とその前のstruct r1;
行は、不透明(OPAQUE)型としてヘッダーに表示される可能性があります。サポート機能のリストは不完全です。struct r1
関数に渡すために初期化されたポインタを取得する方法が必要ですが、それは詳細です。
この2番目のTUでコードを機能させるstruct r1
には、関数を定義する前に、の型がグローバルスコープで完全である必要があります。また、再帰参照のため、`structr21も完全である必要があります。
#include <stdio.h>
/* Logically in a 3-line header file */
struct r1;
extern void z(struct r1 *r1p);
extern void y(struct r1 *r1p);
/* Details private to this TU */
struct r2 { struct r1 *rn; int y; };
struct r1 { struct r2 *rn; int z; };
void y(struct r1 *r1p)
{
struct r2 p = { r1p, 1 };
struct r1 q = { r1p->rn, 2 };
p.rn = &q;
printf("p.y = %d, q.z = %d\n", p.y, q.z);
}
void z(struct r1 *r1p)
{
struct r2 p = { r1p, 1 };
struct r1 q = { r1p->rn, 2 };
p.rn = &q;
printf("p.y = %d, q.z = %d\n", p.y, q.z);
}
パブリックヘッダーファイルでタイプを不完全なままにして、実装ファイルで構造を定義するこのプロセスは、必要に応じて複数の実装ファイルで繰り返すことができますが、複数のTUが完全な構造定義を使用する場合は、定義を配置することをお勧めします。構造を実装するファイル間でのみ共有されるプライベートヘッダーファイル内。プライベートヘッダーがパブリックヘッダーの前にあるか後にあるかは問題ではないことに注意してください。
たぶん、これはすでにあなたにとってすべて明白でした。以前は、このレベルの詳細でそれを熟考する必要はありませんでした。