次のコードがCでコンプライアンス警告をトリガーする(gcc -pedantic
; "typedef redefinition"でテスト)が、C++(g++ -pedantic
)では問題ない理由の標準リファレンスが必要です。
typedef struct Foo Foo;
typedef struct Foo Foo;
int main() { return 0; }
Cでtypedef
を繰り返し定義できないのはなぜですか?
(これは、 Cプロジェクト のヘッダー構造化に実際的な影響を及ぼします。)
C++標準が明示的にそう言っているからです。
参照:
C++ 03標準7.1.3typedef指定子
§7.1.3.2:
特定の非クラススコープでは、typedef指定子を使用して、そのスコープで宣言されている任意の型の名前を再定義し、すでに参照している型を参照できます。
【例:
typedef struct s {/ * ... * /} s;
typedef int I;
typedef int I;
typedef I I;
-例を終了]
typedef
名にはリンケージがなく、C99標準では、リンケージ仕様のない識別子が同じスコープで同じ名前空間に複数の宣言を持つことを許可していません。
参照:
C99標準:§6.2.2識別子のリンク
§6.2.2/ 6状態:
次の識別子にはリンクがありません:オブジェクトまたは関数以外のものとして宣言された識別子;関数パラメータとして宣言された識別子;ストレージクラスなしで宣言されたオブジェクトのブロックスコープ識別子特定の外部。
さらに§6.7/状態:
識別子にリンケージがない場合、同じスコープで同じ名前空間にある識別子の宣言は(宣言子または型指定子で)1つだけでなければなりませんで指定されているタグを除きます。 6.7.2.3。
2011 C規格は、2011年12月19日月曜日にISOによって公開されました(より正確には、公開されたという通知が19日に委員会のWebサイトに追加されました。この規格は「ずっと前」として公開された可能性があります。 2011-12-08として)。 WG14 Webサイトの発表を参照してください。悲しいことに、 ISOからのPDF は338スイスフランの費用がかかります。 および [〜#〜] ansi [〜#〜] 387USDから。
質問は「Cで繰り返しtypedefを使用できますか?」です。答えは「いいえ-ISO/IEC 9899:1999または9899:1990規格にはありません」です。その理由はおそらく歴史的なものです。元のCコンパイラはそれを許可していなかったため、元の標準化ツール(Cコンパイラですでに利用可能なものを標準化することを義務付けられていた)はその動作を標準化しました。
C99標準が繰り返しのtypedefを禁止している場所については、 answer by Als を参照してください。 C11標準は、§6.7¶3のルールを次のように変更しました。
3識別子にリンケージがない場合、次の場合を除いて、同じスコープおよび同じ名前空間での識別子の宣言(宣言子または型指定子内)は1つだけでなければなりません。
- typedef名は、typeが可変的に変更された型でない限り、現在と同じ型を示すように再定義できます。
- タグは、6.7.2.3で指定されているように再宣言できます。
そのため、C11ではtypedefを繰り返すという明示的な義務があります。 C11準拠のCコンパイラーの可用性をロールバックします。
まだC99以前を使用している場合、フォローアップの質問は、おそらく「では、繰り返しのtypedefで問題が発生しないようにするにはどうすればよいですか?」です。
複数のソースファイルで必要な各タイプを定義する単一のヘッダーがあるというルールに従う場合(ただし、そのようなタイプを定義するヘッダーは多数存在する可能性があります。ただし、各個別のタイプは1つのヘッダーにのみ含まれます)。そのヘッダーは、そのタイプが必要なときにいつでも使用され、競合に遭遇することはありません。
型へのポインタのみが必要で、実際の構造を割り当てたり、それらのメンバー(不透明(OPAQUE)型)にアクセスしたりする必要がない場合は、不完全な構造体宣言を使用することもできます。繰り返しますが、どのヘッダーが不完全な型を宣言するかについてのルールを設定し、型が必要な場所でそのヘッダーを使用します。
参照 Cの外部変数とは ;変数について説明していますが、型は多少同じように扱うことができます。
特定の包含を禁止する個別のプリプロセッサの複雑さのために、「不完全な構造宣言」が非常に必要です。つまり、完全なヘッダーによって再度typedefされた場合、これらの前方宣言をtypedefしてはならないということですか?
多かれ少なかれ。私は実際にこれに対処する必要はありませんでした(システムの一部で心配しなければならない部分がありますが)ので、これは少し暫定的ですが、うまくいくはずだと思います。
一般に、ヘッダーは、ライブラリのユーザーがライブラリを使用してコンパイルできるように、「ライブラリ」(1つ以上のソースファイル)によって提供される外部サービスを十分に詳細に記述します。特に複数のソースファイルがある場合は、たとえば完全なタイプを定義する内部ヘッダーも存在する可能性があります。
すべてのヘッダーは、(a)自己完結型であり、(b)べき等です。つまり、(a)ヘッダーを含めることができ、必要な他のすべてのヘッダーが自動的に含まれます。(b)コンパイラーの怒りを招くことなく、ヘッダーを複数回含めることができます。後者は通常ヘッダーガードで実現されますが、_#pragma once
_を好む人もいますが、それは移植性がありません。
したがって、次のようなパブリックヘッダーを作成できます。
_#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
#include <stddef.h> // size_t
typedef struct mine mine;
typedef struct that that;
extern size_t polymath(const mine *x, const that *y, int z);
#endif /* PUBLIC_H_INCLUDED */
_
これまでのところ、それほど物議を醸しているわけではありません(ただし、このライブラリによって提供されるインターフェイスが非常に不完全であると正当に疑うことができます)。
_#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include "public.h" // Get forward definitions for mine and that types
struct mine { ... };
struct that { ... };
extern mine *m_constructor(int i);
...
#endif /* PRIVATE_H_INCLUDED */
_
繰り返しますが、あまり物議を醸すものではありません。 _public.h
_ヘッダーを最初にリストする必要があります。これにより、自己完結の自動チェックが提供されます。
polymath()
サービスを必要とするコードは次のように記述します。
_#include "public.h"
_
これが、サービスを使用するために必要なすべての情報です。
polymath()
サービスを定義するライブラリ内のコードは次のように記述します。
_#include "private.h"
_
その後、すべてが正常に機能します。
multimath()
サービスを使用する別のライブラリ(polymath()
と呼びます)がある場合、そのコードには、他のコンシューマーと同じように_public.h
_が含まれます。 polymath()
サービスがmultimath()
への外部インターフェースの一部である場合、_multimath.h
_パブリックヘッダーには_public.h
_が含まれます(申し訳ありませんが、終わり近くで用語を切り替えました、 ここに)。 multimath()
サービスがpolymath()
サービスを完全に隠蔽している場合、_multimath.h
_ヘッダーには_public.h
_は含まれませんが、multimath()
プライベートヘッダーはそうするかもしれません、あるいはpolymath()
サービスを必要とする個々のソースファイルは必要なときにそれを含めることができます。
どこにでも正しいヘッダーを含めるという規律に忠実に従う限り、二重定義の問題に遭遇することはありません。
その後、ヘッダーの1つに2つの定義グループが含まれていることがわかった場合、1つは競合なしで使用でき、もう1つは新しいヘッダー(およびそこで宣言されているサービス)と時々(または常に)競合する可能性があります。元のヘッダーを2つのサブヘッダーに分割します。各サブヘッダーは、ここで詳しく説明したルールに従います。元のヘッダーは簡単になります。ヘッダーガードと2つの個別のファイルを含める行です。依存関係は変更されますが(依存する追加ファイル)、既存のすべての作業コードは変更されません。新しいコードには、元のヘッダーと競合する新しいヘッダーを使用しながら、関連する受け入れ可能なサブヘッダーを含めることができるようになりました。
もちろん、単純に調整できない2つのヘッダーを持つことができます。不自然な例として、(FILE
構造の異なるバージョン(_<stdio.h>
_のバージョンとは異なる)を宣言する(不適切に設計された)ヘッダーがある場合、あなたはうんざりしています。コードには、不適切に設計されたヘッダーまたは_<stdio.h>
_のいずれかを含めることができますが、両方を含めることはできません。この場合、不適切に設計されたヘッダーは、新しい名前(おそらく、File
ですが、おそらく他の名前)を使用するように修正する必要があります。データベース接続用の_DB_Connection
_などのいくつかの一般的なデータ構造を使用して、企業買収後に2つの製品のコードを1つにマージする必要がある場合、この問題がより現実的に発生する可能性があります。 C++ namespace
機能がない場合、一方または両方のコードの名前を変更する必要があります。
7.1.3/3および/ 4のため、C++でそれを行うことができます。
6.7.7には同等の特殊なケースがないため、C99では実行できません。したがって、typedef名の再宣言は、他の識別子の再宣言と同じルールに従います。具体的には、6.2.2/6(typedefにはリンケージがありません)および6.7/3(リンケージのない識別子は同じスコープで一度だけ宣言できます)。
typedef
はC99ではstorage-class-specifierですが、C++ではdecl-specifierであることに注意してください。文法が異なると、C++の作成者は、typedefを「異なる種類の宣言」にすることにもっと力を入れることにしたのではないかと思うので、特別なルールにもっと時間とテキストを費やすことをいとわなかったのかもしれません。それを超えて、私はC99作者の(欠如した)動機が何であったかを知りません。
[編集:C1xに関するヨハネスの回答を参照してください。私はそれをまったくフォローしていないので、おそらく「C」を「C99」を意味するために使用するのをやめるべきです。 「C」は「C99」を意味するはずですが、実際には「運が良ければC99ですが、MSVCをサポートする必要がある場合はC89」を意味します。]
[もう一度編集してください。実際、公開されており、現在はC11です。うわー。]
多くの人が標準を参照して答えましたが、なぜここでCとC++の標準が異なるのか誰も言いませんでした。そうですね、C++で繰り返しtypedefを許可する理由は、C++が構造とクラスを型として暗黙的に宣言しているためだと思います。したがって、C++では次のことが有効です。
struct foo { int a; int b; };
foo f;
Cでは、次のように書く必要があります。
struct foo { int a; int b; };
typedef struct foo foo;
foo f;
構造体を型として宣言する、そのようなCコードはたくさんあります。このようなコードがC++に移行されると、C++言語が独自の暗黙的なtypedefを追加するため、typedefが重複します。そのため、プログラマーが不要になったtypedefを削除する手間を省くために、最初からC++で重複するtypedefを許可していました。
他の人が言っているように、時間のある人々は、Cで同じtypedefを繰り返し許可することも役立つ可能性があることに気づきました。少なくとも、害はないはずです。そのため、このC++機能はC11に「バックポート」されました。
C仕様には、whyこれは無効であると書かれているものはありません。仕様はそれを明確にするために間違った場所です。 FWIWそれはC1xで許可されています(私の最後の質問の1つに対して受け取った回答によると)。
このc1x機能は、マクロのtypedefへの変換をサポートしていると思います(前者は、同じ場合は繰り返すことができます)。