23

gccがvoid型のextern宣言を許可するのはなぜですか?これは拡張機能ですか、それとも標準Cですか?これの許容できる使用法はありますか?

これは拡張機能だと思いますが、http:
//gcc.gnu.org/onlinedocs/gcc-4.3.6/gcc/C-Extensions.htmlで言及されていません。

$ cat extern_void.c
extern void foo; /* ok in gcc 4.3, not ok in Visual Studio 2008 */
void* get_foo_ptr(void) { return &foo; }

$ gcc -c extern_void.c # no compile error

$ gcc --version | head -n 1
gcc (Debian 4.3.2-1.1) 4.3.2

fooをvoid型として定義することは、もちろんコンパイルエラーです。

$ gcc -c -Dextern= extern_void.c
extern_void.c:1: error: storage size of ‘foo’ isn’t known

比較のために、VisualStudio2008はextern宣言でエラーを出します。

$ cl /c extern_void.c 
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

extern_void.c
extern_void.c(1) : error C2182: 'foo' : illegal use of type 'void'
4

4 に答える 4

8

奇妙なことに (または、それほど奇妙なことではないかもしれませんが...)、gcc はこれを受け入れるのが正しいように見えます。

これがstaticの代わりに宣言された場合extern、内部リンケージがあり、§6.9.2/3 が適用されます。

オブジェクトの識別子の宣言が暫定的な定義であり、内部リンケージがある場合、宣言された型は不完全な型であってはなりません。

ストレージ クラス (externこの場合は ) が指定されていない場合は、§6.7/7 が適用されます。

オブジェクトの識別子がリンケージなしで宣言されている場合、オブジェクトの型は、宣言子の終わりまでに、または初期化子がある場合は init-declarator の終わりまでに完全でなければなりません。関数の引数 (プロトタイプを含む) の場合、完全である必要があるのは調整された型 (6.7.5.3 を参照) です。

( §6.2.5 void/19):

void 型 [...] は、完成できない不完全型です。

ただし、それらのどれにも当てはまりません。それは §6.7.2/2 の要件のみを残しているようです。これは、 type を持つ名前の宣言を許可しているようですvoid:

各宣言の宣言指定子、および各構造体宣言と型名の指定子修飾子リストに、少なくとも 1 つの型指定子を指定する必要があります。型指定子の各リストは、次のセットのいずれかになります (1 行に複数のセットがある場合は、コンマで区切ります)。型指定子は任意の順序で発生する可能性があり、他の宣言指定子と混在する可能性があります。

  • 空所
  • チャー
  • 署名された文字

[ ... その他のタイプは省略]

それがvoid本当に意図的なものかどうかはわかりません。派生型 (void へのポインターなど) や関数からの戻り値の型などを意図したものだと思いますが、その制限を直接指定するものは見つかりません。

于 2012-04-06T05:23:29.590 に答える
6

宣言するための唯一の正当な使用法を見つけました

extern void foo;

は、foo指定されていない型のオブジェクトのアドレスを示すリンク シンボル (リンカによって定義された外部シンボル) です。

リンクシンボルはメモリの範囲を伝えるためによく使用されるため、これは実際に役立ちます。つまり、.text セクションの開始アドレス、.text セクションの長さなどです。

そのため、これらのシンボルを使用するコードでは、シンボルを適切な値にキャストすることによってその型を文書化することが重要です。たとえば、foo実際にはメモリ領域の長さの場合:

uint32_t textLen;

textLen = ( uint32_t )foo;

または、fooが同じメモリ領域の開始アドレスの場合:

uint8_t *textStart;

textStart = ( uint8_t * )foo;

私が知っている「C」でリンクシンボルを参照する唯一の代替方法は、それを外部配列として宣言することです。

extern uint8_t foo[];

voidリンカーで定義されたシンボルに固有の「型」がないことが明確になるため、私は実際には宣言を好みます。

于 2013-09-27T15:43:11.447 に答える
1

GCC (および LLVM C フロントエンド) には間違いなくバグがあります。ただし、Comeau と MS の両方がエラーを報告しているようです。

OP のスニペットには、少なくとも 2 つの明確な UB と 1 つのレッドニシンがあります。

N1570から

[UB #1]mainホスト環境で欠落:

J2。未定義の動作

[...] ホスト環境のプログラムは、指定された形式 (5.1.2.2.1) のいずれかを使用して main という名前の関数を定義しません。

void[UB #2] 上記を無視しても、明示的に禁止されている式のアドレスを取得するという問題がまだ残っています。

6.3.2.1 左辺値、配列、および関数指定子

1 左辺値は、潜在的にオブジェクトを指定する式 (void 以外のオブジェクト型を持つ) です。64)

と:

6.5.3.2 アドレスおよび間接演算子

制約

1 単項 & 演算子のオペランドは、関数指定子、[] または単項 * 演算子の結果、またはビットフィールドではなく、レジスタ ストレージ クラスで宣言されていないオブジェクトを指定する左辺値のいずれかでなければなりません。指定子。

[注: lvalue mineに重点を置く] また、標準には次のセクションがありますvoid

6.3.2.2 ボイド

1 void 式 (void 型を持つ式) の (存在しない) 値は、どのような方法でも使用してはならず、暗黙的または明示的な変換 (void を除く) をそのような式に適用してはなりません。

ファイル スコープ定義は primary-expression (6.5) です。したがって、 は で示されるオブジェクトのアドレスを取得していますfoo。ところで、後者はUBを呼び出します。したがって、これは明示的に除外されます。extern解決すべきことは、修飾子を削除すると上記が有効になるかどうかです。

私たちの場合、foo§6.2.2/5 によると:

5 [...] オブジェクトの識別子の宣言にファイル スコープがあり、ストレージ クラス指定子がない場合、そのリンケージは外部です。

つまり、externを省略したとしても、同じ問題が発生します。

于 2012-04-07T12:05:34.987 に答える
1

C のリンカ相互作用セマンティクスの 1 つの制限は、数値のリンク時定数を許可するメカニズムを提供しないことです。一部のプロジェクトでは、静的イニシャライザに、コンパイル時には使用できないがリンク時には使用できる数値を含める必要がある場合があります。int一部のプラットフォームでは、アドレスが にキャストされた場合に目的の数値が得られるラベルをどこか (アセンブリ言語ファイルなど) で定義することで、これを実現できます。次に、extern定義を C ファイル内で使用して、その「アドレス」をコンパイル時の定数として使用できるようにします。

このアプローチは (アセンブリ言語を使用する場合と同様に) プラットフォーム固有ですが、それ以外の場合は問題となるいくつかの構造を可能にします。それのやや厄介な側面は、ラベルが C で のような型として定義されている場合unsigned char[]、アドレスが逆参照されているか、演算が実行されている可能性があるという印象を与えることです。コンパイラが を受け入れる場合、 はvoid foo;、他の void* に適用できるのと同じポインタから整数へのセマンティクスを使用して(int)&foo、リンカによって割り当てられたアドレスを整数に変換します。foo

voidその目的で使用したことはないと思いますが(常に使用していました)、何かがそれを正当な拡張として定義した場合、よりクリーンにextern unsigned char[]なると思います(C標準では、void特定の非 void 型以外のものとして使用できるリンカ シンボル; C プログラムが として定義できるリンカ識別子を作成する手段が存在しないプラットフォームではextern void、コンパイラがそのような構文を許可する必要はありません)。

于 2015-04-28T18:39:57.510 に答える