web-dev-qa-db-ja.com

Cのvoid型

Cのvoid型は、さまざまな状況から奇妙に思われます。 intcharなどの通常のオブジェクトタイプのように動作する場合もあれば、何も意味しない場合もあります(本来あるべきことです)。

私のスニペットを見てください。まず第一に、あなたがdeclarevoidオブジェクトを作成できるのは奇妙に思えます。つまり、何も宣言しないということです。

次に、int変数を作成し、その結果をvoidにキャストして、破棄しました。

他のタイプの式がvoid式として評価された場合、その値または指定子は破棄されます。 (ISO/IEC 9899:201x、6.3.2.2 void)

voidキャストで関数を呼び出そうとしましたが、コンパイラーから(Clang 10.0)が返されました。

error: too many arguments to function call, expected 0, have 1

したがって、プロトタイプのvoidnothingを意味し、タイプvoidではありません。

しかし、その後、voidへのポインターを作成し、それを逆参照して、「result」をintに割り当てました。変数。 「互換性のないタイプ」エラーが発生しました。 つまり、void型がここに存在します。

extern void a; // Why is this authorised ???

void foo(void); // This function takes no argument. Not the 'void' type.

int main(void)
{
    int a = 42;
    void *p;

    // Expression result casted to 'void' which discards it (per the C standard).
    (void)a;

    // Casting to 'void' should make the argument inexistant too...
    foo((void)a);

    // Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
    a = *p;

    // Am I not passing the 'void' type ?
    foo(*p);

    return 0;
}

voidは実際のタイプですか、それとも何も意味しないキーワードですか? 「ここでは何も許可されていません」という命令のように動作することもあれば、実際の型のように動作することもあるためです。

[〜#〜] edit [〜#〜]:この質問は[〜#〜] not [〜#〜]重複しています。これは、純粋にvoid型のセマンティクスに関するものです。 voidの使用方法、voidへのポインタ、その他についての説明は必要ありません。 C規格に準拠した回答が欲しい。

7
eigenslacker

C言語では、void型が「null」または「nothing」よりも「ドントケア」の意味で導入されており、さまざまなスコープで使用されています。

voidキーワードは、void typereference to voidvoid expressionvoid operand、またはvoid functionを参照できます。また、パラメーターを持たない関数を明示的に定義します。

それらのいくつかを見てみましょう。


voidタイプ

まず第一に、ISO/IEC 9899:2017、§6.2.5タイプで述べられているように、voidオブジェクトが存在し、いくつかの特別なプロパティがあります。

  1. Voidタイプは、空の値のセットで構成されます。完了できない不完全なオブジェクトタイプです。

ポインタ

より便利なreference to voidまたはvoid *は、不完全な型への参照ですが、それ自体は明確に定義されており、完全な型であり、サイズがあり、他の標準変数として使用できます。 ISO/IEC 9899:2017、§6.2.5タイプに記載されているように:

  1. Voidへのポインタは、文字タイプへのポインタと同じ表現および配置要件を持つ必要があります。

    同様に、互換性のあるタイプの修飾バージョンまたは非修飾バージョンへのポインターは、同じ表現および配置要件を持つ必要があります。

    構造体タイプへのすべてのポインタは、互いに同じ表現および配置要件を持つ必要があります。

    共用体タイプへのすべてのポインターは、互いに同じ表現および配置要件を持つ必要があります。

    他のタイプへのポインタは、同じ表現または配置要件を持つ必要はありません。


voidへのキャスト

castとして使用して式を無効にすることができますが、そのような式の副作用の完了を許可します。この概念は、標準のISO/IEC 9899:2017、§6.3変換、§6.3.2.2voidで説明されています。

  1. Void式(void型の式)の(存在しない)値は、いかなる方法でも使用してはならず、暗黙的または明示的な変換(voidを除く)はそのような式に適用されないものとします。

    他のタイプの式がvoid式として評価された場合、その値または指定子は破棄されます。 (void式はその副作用について評価されます。

voidへのキャストの実際的な例は、関数定義で未使用のパラメーターの警告を防ぐための使用です。

int fn(int a, int b)
{
    (void)b;    //This will flag the parameter b as used 

    ...    //Your code is here

    return 0;
}

上記のスニペットは、コンパイラの警告をミュートするために使用される標準的な方法を示しています。パラメータvoidbへのキャストは、コードを生成しない効果的な式として機能し、コンパイラの不満を防ぐためにbを使用済みとしてマークします。


void関数

標準の段落§6.3.2.2voidは、式で使用可能な値を返さない関数であるvoid関数についての説明もカバーしていますが、関数はとにかく副作用を実装するために呼び出されます。


voidポインタのプロパティ

前に述べたように、voidへのポインタは、ISO/IEC 9899:2017、§6.3.2.3ポインタ)で説明されているプロパティにより、一般的な方法でオブジェクト参照を処理できるため、はるかに便利です。

  1. Voidへのポインタは、任意のオブジェクトタイプへのポインタとの間で変換できます。

    任意のオブジェクトタイプへのポインタをvoidへのポインタに変換して、再び戻すことができます。結果は元のポインタと同じになります

実際の例として、入力パラメーターに応じて異なるオブジェクトへのポインターを返す関数を想像してみてください。

enum
{
    FAMILY,     //Software family as integer
    VERSION,    //Software version as float
    NAME        //Software release name as char string
} eRelease;

void *GetSoftwareInfo(eRelease par)
{
    static const int   iFamily  = 1;
    static const float fVersion = 2.0;
    static const *char szName   = "Rel2 Toaster";

    switch(par)
    {
        case FAMILY:
            return &iFamily;
        case VERSION:
            return &fVersion;
        case NAME:
            return szName;
    }
    return NULL;
}

このスニペットでは、入力par値に依存できる汎用ポインターを返すことができます。


関数パラメーターとしてのvoid

関数定義でのvoidパラメータの使用は、いわゆるANSI-Standardの後に導入され、可変数の引数を持つ関数を引数なしを持つ関数から効果的に明確にしました。

標準からISO/IEC 9899:2017、6.7.6.3関数宣言子(プロトタイプを含む)

  1. リスト内の唯一の項目としてのタイプvoidの名前のないパラメーターの特殊なケースは、関数にパラメーターがないことを指定します。

実際のコンパイラは、下位互換性のために空の括弧を使用した関数宣言を引き続きサポートしますが、これは廃止された機能であり、将来の標準リリースで最終的に削除される予定です。 将来の方向性-§6.11.6関数宣言子を参照してください:

  1. 空の括弧付きの関数宣言子(プロトタイプ形式のパラメーター型宣言子ではない)の使用は、廃止された機能です。

次の例を考えてみましょう。

int foo();         //prototype of variable arguments function (backward compatibility)
int bar(void);     //prototype of no arguments function
int a = foo(2);    //Allowed
int b = foo();     //Allowed
int c = bar();     //Allowed
int d = bar(1);    //Error!

次のように関数barを呼び出すと、テストに似ています。

int a = 1;
bar((void)a);

オブジェクトをvoidにキャストしてもヌルにならないため、エラーがトリガーされます。したがって、voidオブジェクトをパラメータとして持っていない関数に渡そうとしています。


副作用

要求に応じて、これは副作用概念の簡単な説明です。

副作用とは、ステートメントの実行から派生したオブジェクトと値の変更であり、直接期待される効果ではありません。

int a = 0;
(void)b = ++a;

上記のスニペットでは、void式は直接効果を失い、bを割り当てますが、副作用としてaの値を増やします。

標準での意味を説明する唯一の参照は5.1.2.3プログラム実行にあります:

  1. 揮発性オブジェクトへのアクセス、オブジェクトの変更、ファイルの変更、またはこれらの操作のいずれかを実行する関数の呼び出しはすべて副作用であり、実行環境の状態の変化です。

    一般に、式の評価には、値の計算と副作用の開始の両方が含まれます

8
Frankie_C

voidはタイプです。 C 2018 6.2.5 19によると、型には値がなく(表現できる値のセットが空)、不完全であり(サイズが不明)、完了できません(サイズが不明です)。

_extern void a;_に関しては、これはオブジェクトを定義しません。識別子を宣言します。 aが式で使用された場合(sizeofまたは__Alignof_演算子の一部を除く)、プログラムのどこかにその定義が必要になります。厳密に準拠したCではvoidオブジェクトを定義できないため、式でaを使用することはできません。したがって、この宣言は厳密に準拠したCで許可されていると思いますが、役に立ちません。これは、Cの実装で、型が不明なオブジェクトのアドレスを取得できるようにする拡張機能として使用される場合があります。 (たとえば、あるモジュールで実際のオブジェクトaを定義し、別のモジュールでそれを_extern void a;_として宣言し、そこで_&a_を使用してアドレスを取得します。)

パラメータリストとして_(void)_を使用した関数の宣言は、ごまかしです。理想的には、C++の場合のように、_()_を使用して関数がパラメーターを受け取らないことを示すことができます。ただし、Cの歴史により、_()_は未指定のパラメーターリストを意味するために使用されていたため、パラメーターがないことを意味する別の何かを発明する必要がありました。そのため、_(void)_が採用されました。したがって、_(void)_は、_(int)_はintをとる関数、_(double)_はdoubleをとる関数などという規則の例外です。 —_(void)_は、関数がvoidを受け取るのではなく、パラメーターを受け取らないことを意味する特殊なケースです。

foo((void) a)では、キャストは値を「存在しない」にしません。 aをタイプvoidに変換します。結果は、タイプvoidの式です。その式は「存在します」が、値がなく、式で使用できないため、foo((void) a)で使用するとエラーメッセージが表示されます。

10

C Standard#6.2.5p19から:

19 voidタイプは、空の値のセットで構成されます。完了できない不完全なオブジェクトタイプです。

これは、voidタイプが存在することを示しています。

疑い1:

_void foo(void); // This function takes no argument. Not the 'void' type.
_

正しい。
C Standard#6.7.6.3p10[emphasis mine]から:

10リスト内の唯一の項目としてのvoid型の名前のないパラメーターの特殊なケースは、関数にパラメーターがないを指定します。

void foo();はすでに別の意味を持っているため、これは言語構文に追加する必要がある特殊なケースです(void foo();fooのパラメーターについて何も指定していません)。 void foo();の古い意味がなかったとしたら、void foo();は引数のない関数を宣言するための構文でした。これから何も一般化することはできません。これは特別な場合です。

疑い2:

_// Casting to 'void' should make the argument inexistant too...
foo((void)a);
_

いいえ、不完全ですが、voidもオブジェクトタイプであるためではありません。

疑い3:

_// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;
_

はい、存在するため、コンパイラはこのステートメントでエラーを報告しています。

疑い4:

_// Am I not passing the 'void' type ?
foo(*p);
_

foo()関数の宣言:

_void foo(void);
         ^^^^
_

パラメータリストのvoidは、関数がパラメータなしで宣言されているため、関数が引数を取らないことを示しています。
参考までに、C Standard#5.1.2.2.1p1[emphasis mine]からこれを確認してください。

1プログラムの起動時に呼び出される関数の名前はmainです。実装は、この関数のプロトタイプを宣言していません。戻り値の型がintでパラメータなし:で定義されます。

_    int main(void) { /* ... */ }
             ^^^^
_

疑い5:

_extern void a; // Why is this authorised ???
_

voidは有効なタイプであり、単なる宣言であるため、これは許可されています。 aにストレージは割り当てられません。

6
H.S.

まず第一に、voidオブジェクトを宣言できるのは奇妙に思えます。つまり、何も宣言しないということです。

voidは、完了できない不完全なオブジェクトタイプです。これは主に、通常コンテキスト、つまりvoid特別な扱いを提供しないコンテキストでの使用を定義します。 extern宣言は、そのような通常のコンテキストの1つです。非定義宣言で不完全なデータ型を使用しても問題ありません。

ただし、その宣言に一致する定義を提供することはできません。

したがって、プロトタイプのvoidは何も意味せず、void型ではありません。

正しい。パラメータには名前を付けないでください。そして、(void)の組み合わせが与えられます特別な扱い:それはタイプvoidの1つのパラメーターではなく、パラメーターがまったくありません。

しかし、その後、voidへのポインターを作成し、それを逆参照して、「結果」をint変数に割り当てました。 「互換性のないタイプ」エラーが発生しました。つまり、void型がここに存在します。

いいえ。単項*演算子をvoid *ポインタに適用することは違法です。あなたのコードはその理由ですでに無効です。コンパイラが誤解を招く診断メッセージを発行しました。正式には、問題の原因を適切に説明するために診断メッセージは必要ありません。コンパイラは「こんにちは!」と言っただけかもしれません。

Voidは実際のタイプですか、それとも何も意味しないキーワードですか?

タイプです。完了できない不完全なオブジェクトタイプです。

2
AnT

[〜#〜] c [〜#〜]では、voidデータ型と見なすことはできません、これは、実際にはデータがないことを示すために、データ型の代わりにプレースホルダーとして使用されるキーワードです。したがって、これ

_void a;
_

有効ではない。

ここにいる間

_void foo(void); 
_

voidキーワードは、fooが入力引数を受け取らないこと、または戻り値の型を持たないことをコンパイラーに通知するために使用されます。

以下の場合

_int a = 42;
void *p;
a = *p; /* this causes error */
_

_a = *p;_は間違っています。これは、voidポインターを直接逆参照できないためです。最初に、適切な型キャストを実行する必要があります。例:

_a = *(int*)p; /* first typecast and then do dereference */
_

またこれ

_foo(*p);
_

2つの理由で間違っています、

  • まず、foo()は引数を期待していません。
  • 次に、pはvoidポインターであるため、_*p_を実行できません。 foo(*(int*)p);宣言がfoo()の場合、正しいものはvoid foo(int);です。

これに注意してください

_ (void)a;
_

何もしないので、コンパイラは警告を出さないかもしれませんが、好きなときに

_int b = (void)a;
_

voidはデータ型と見なされないため、コンパイラは許可しません。

最後にこれ

_extern void a; // Why is this authorised ???
_

これは単なる宣言であり、定義ではありません。aは定義するまで存在しません。aにはexternストレージクラスがあるため、どこかで定義する必要があります。次のように定義します

_a = 10;
_

コンパイラは次のようにエラーをスローします

エラー:「a」の型が不完全です

から[〜#〜] c [〜#〜]標準6.2.5タイプ

voidタイプは_empty set of values_で構成されます。 完了できない不完全なオブジェクトタイプ

6.3.2.2 void

void式(void型の式)の(存在しない)値いかなる方法でも使用してはならず、暗黙的または明示的な変換(voidを除く)はそのような式には適用されません。式。他のタイプの式がvoid式として評価される場合、その値または指定子は破棄されます。 (void式はその副作用について評価されます。)

6.3.2.3ポインター

voidへのポインタ任意のオブジェクトタイプへのポインタとの間で変換できます。任意のオブジェクトタイプへのポインタは、voidへのポインタに変換されて再び戻る可能性があります。結果は元のポインタと同じになります。

_storage-class specifier_または型修飾子は、キーワードvoidを関数パラメーター型リスト(6.7.6.3)として変更します。

void式の値を使用しようとするか、暗黙的または明示的な変換(voidを除く)がvoid式に適用されます(6.3.2.2)。

2
Achal