Cのvoidがvoidではないのはなぜですか?


25

JavaやC#などの厳密に型指定された言語では、メソッドの戻り値型としてvoid(またはVoid)を意味するようです:

このメソッドは何も返しません。なし。返品不可。このメソッドからは何も受け取りません。

本当に奇妙なのは、Cではvoid、戻り値の型として、またはメソッドのパラメーターの型としても、

それは本当に何でもありえます。調べるには、ソースコードを読む必要があります。がんばろう。それがポインタである場合、あなたは何をしているかを本当に知っている必要があります。

Cの次の例を検討してください。

void describe(void *thing)
{
    Object *obj = thing;
    printf("%s.\n", obj->description);
}

void *move(void *location, Direction direction)
{
    void *next = NULL;

    // logic!

    return next;
}

明らかに、2番目のメソッドはポインターを返しますが、これは定義上何でもかまいません。

CはJavaやC#よりも古いので、なぜこれらの言語はvoid「何も」を意味するものとして採用したのに、Cは「何も何も(ポインターの場合)」として使用しませんでしたか?


138
質問のタイトルとテキストはvoid、コード例で使用されvoid*ているものとはまったく異なります。

4
また、JavaとC#には同じ概念があり、Object明確にするために1つのケースで単純に呼び出すことに注意してください。
Mooingダック14

4
@Mephy、厳密に型指定されていませんか?ダックタイピングを使用するC#dynamicとは、めったに使用されないタイプを意味しますか?
Axarydax 14

9
@Mephy:前回、Wikipedia で「強く型付けされた」という相互に矛盾する11個の意味をリストアップしました。したがって、「C#は強く型付けされていません」と言っても意味がありません。
エリックリッパー14

8
@LightnessRacesinOrbit:いいえ、重要なのは、用語の正式な定義がないことです。ウィキペディアは、用語の正しい意味を教えてくれる規範的なリソースではありません。これは説明的なリソースです。11種類の矛盾した意味を記述しているという事実は、慎重に定義しなければ会話この用語を使用できない証拠です。そうしないと、会話中の人々がお互いにではなく、過去に話している確率が非常に高いことを意味します。
エリックリッパー14

回答:


58

キーワードvoid(ポインターではない)は、これらの言語では「何もない」ことを意味します。これは一貫しています。

ご指摘のとおり、void*生のポインター(CおよびC ++)をサポートする言語での「あらゆるものへのポインター」を意味します。あなたが言及したように、それはvoid2つの異なることを意味するので、これは不幸な決定です。

再利用の背後にある歴史的な理由voidは、さまざまな文脈で「何もない」と「何でも」という意味で見つけることができませんでしたが、Cは他のいくつかの場所でこれを行います。たとえばstatic、さまざまなコンテキストでさまざまな目的があります。C言語には、この方法でキーワードを再利用することの前例があることは明らかです。慣習についてどう思うかに関係ありません。

JavaとC#は、これらの問題のいくつかを修正するために完全にブレークするほど十分に異なっています。Javaと「安全な」C#も生のポインターを許可せず、簡単なC互換性を必要としません(安全でないC#ポインターを許可しますが、C#コードの大部分はこのカテゴリーに分類されません)。これにより、下位互換性を心配することなく、少し変更することができます。これを行う1つの方法はObject、すべてのクラスが継承する階層のルートにクラスを導入することです。そのため、Object参照は、void*型の問題や生メモリ管理の煩わしさなしに同じ機能を果たします。


2
私は家に帰るとき、私は参照を提供します

4
They also do not allow raw pointers and do not need easy C compatibility.偽。C#にはポインターがあります。それらは通常オフ(通常はオフ)になっていますが、そこにあります。
メイガス14

8
それらが再利用された理由についてvoid:明らかな理由は、新しいキーワードの導入(および潜在的に既存のプログラムの破壊)を避けることです。それらが特別なキーワード(例えばunknown)を導入した場合、それvoid*は無意味な(おそらく違法な)構造になりunknown、の形式でのみ合法になりますunknown*
ジェームズリン14

20
でも中にvoid *void手段「何も」ではなく、「何でも」。aを逆参照することはできませんvoid *。最初に別のポインタ型にキャストする必要があります。
oefe 14

2
@oefeその髪を分割することは無意味だと思います。なぜならvoid、とvoid*は異なるタイプだからです(実際にvoidは「タイプなし」です)。「intin int*は何かを意味する」と言うのと同じくらい理にかなっています。いいえ、ポインタ変数を宣言すると、「これは何かを保持できるメモリアドレスです」と言っています。の場合void*、あなたは「このアドレスは何かを保持しているが、ポインタのタイプから何かを推測することはできない」と言っいます。

32

voidそしてvoid*2つの異なるものです。 voidCの場合、Javaの場合とまったく同じことを意味し、戻り値がありません。A void*は型のないポインターです。

Cのすべてのポインターは、逆参照できる必要があります。aを逆参照した場合void*、どのタイプを取得する予定ですか?Cポインターは実行時の型情報を保持しないため、型はコンパイル時に認識されている必要があります。

そのコンテキストを考えると、間接参照で論理的にできることvoid*はそれを無視することだけです。これはまさにvoid型が示す動作です。


1
「無視」ビットに到達するまで同意します。返されたものに、その解釈方法に関する情報が含まれている可能性があります。言い換えれば、BASIC Variantに相当するCである可能性があります。「これを入手したら、それが何であるかを調べてください-あなたが何を見つけるかを前もって伝えることはできません」。
フローリス14

1
別の言い方をすれば、仮想的には、ポインターでアドレス指定されたメモリ位置の最初のバイトにプログラマー定義の「タイプ記述子」を置くことができます。
ロバートハーベイ14

1
もちろん、Cでは、これらの詳細を言語に焼き付けるのではなく、プログラマーに任せることにしました。言語の視点、それは無視する以外の操作を行うために他に何を知っていません。これにより、ほとんどのユースケースで静的に決定できる場合、常に最初のバイトとして型情報を含めるオーバーヘッドを回避できます。
カールビーレフェルト14

4
@RobertHarveyはい、ただし、無効なポインターを逆参照しているわけではありません。voidポインターをcharポインターにキャストしてから、charポインターを逆参照しています。
user253751 14

4
@Floris gccはあなたに同意しません。値を使用しようとgccすると、エラーメッセージが表示されますerror: void value not ignored as it ought to be
カスペルド14

19

おそらくvoid、戻り値の型と考える方が便利でしょう。次に、2番目のメソッドは「型指定されていないポインターを返すメソッド」を読み取ります。


それが役立ちます。オブジェクト指向言語に何年も費やした後(最終的には)Cを学習している人として、ポインターは私にとって非常に新しい概念です。ポインターを返すとき、ActionScript 3のような単純に「任意の戻り値の型」を意味する言語voidとほぼ同等の*ようです。
ナフトゥリケイ14

5
まあ、voidワイルドカードではありません。ポインタは単なるメモリアドレスです。「*このポインタが参照するメモリアドレスで見つけることができるデータ型は次のとおりです」という前に型を置きます。コンパイラーに「型がわからない。生のポインターを返すだけで、それが指すデータをどうするかを理解する」と言うvoid前に置き*ます。
ロバートハーヴェイ14

2
私はvoid*「ボイド」を指しているとさえ言うでしょう。何も指していない裸のポインター。それでも、他のポインターと同様にキャストできます。
ロバート14

11

用語を少し強化しましょう。

以下からのオンラインC 2011標準

6.2.5タイプ
...
19 voidタイプは空の値のセットで構成されます。完了できない不完全なオブジェクトタイプです。
...
6.3コンバージョン
...
6.3.2.2ボイド

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

6.3.2.3ポインタ

1へのポインタvoid任意のオブジェクト型へのポインタとの間で変換できます。任意のオブジェクト型へのポインターは、voidへのポインターに変換され、再び元に戻されます。結果は元のポインターと等しくなります。

void(それが副作用を有することができるが)式は値を持ちません。を返すvoidように定義された関数がある場合:

void foo( void ) { ... }

その後、呼び出し

foo();

値に評価されません。結果がないため、結果を何にも割り当てることはできません。

ポインタには、void基本的に「汎用」ポインタ型です。void *明示的なキャストを必要とせずに、他のオブジェクトポインター型に値を割り当てることができます(これが、すべてのCプログラマーがの結果をキャストしたことを叫ぶ理由ですmalloc)。

void *;を直接間接参照することはできません。指示先オブジェクトにアクセスする前に、最初に別のオブジェクトポインタータイプに割り当てる必要があります。

voidポインターは(多かれ少なかれ)汎用インターフェイスを実装するために使用されます。標準的な例はqsortライブラリ関数です。これは、タイプを認識する比較関数を提供する限り、任意のタイプの配列をソートできます。

はい、2つの異なる概念(値なしと汎用ポインター)に同じキーワードを使用するのは混乱しますが、前例がないようなものではありません。staticCとC ++の両方で複数の異なる意味を持ちます。


9

JavaおよびC#では、メソッドの戻り値の型としてのvoidは、どういう意味かと思われます。このメソッドは何も返しません。なし。返品不可。このメソッドからは何も受け取りません。

その文は正しいです。CおよびC ++でも同様です。

Cでは、戻り値の型として、またはメソッドパラメーターの型としても、何か別の意味があります。

その文は間違っています。voidCまたはC ++の戻り値の型は、C#およびJavaの場合と同じことを意味します。と混同voidしていvoid*ます。それらは完全に異なっています。

それは紛らわしいことではなく、2つのまったく異なることvoidvoid*意味していませんか?

うん。

ポインターとは何ですか?voidポインターとは何ですか?

ポインタがあることができる間接参照します有効なポインターを間接参照すると、ポイント先の型の格納場所が提供されます。ボイドポインタは特に尖った-のタイプを持っていないポインタです。値を間接参照して記憶場所を作成する前に、より具体的なポインタ型に変換する必要があります。

C、C ++、Java、C#でvoidポインターは同じですか?

Javaにはvoidポインターがありません。C#はそうします。これらは、CおよびC ++と同じです。特定の型が関連付けられていないポインター値で、参照を解除してから格納場所を生成する前に、より特定の型に変換する必要があります。

CはJavaやC#よりも古いので、なぜこれらの言語はvoidを「無」を意味するものとして採用し、Cは「無または何か(ポインターの場合)」として使用したのですか?

それは虚偽を前提としているため、質問は一貫性がありません。より良い質問をしてみましょう。

JavaとC#が、voidたとえば、関数がvoidを返すことはなく、サブルーチンは常にvoidを返すというVisual Basicの規則を使用する代わりに、有効な戻り値型の規則を採用したのはなぜですか?

JavaまたはC#を使用する言語からvoid戻り型のプログラマーに馴染みがあるようにするため。

なぜC#は、void*意味がまったく異なる混乱を招く慣習を採用したのvoidですか?

void*ポインター型の言語からC#に来るプログラマーに馴染みがあるようにするため。


5
void describe(void *thing);
void *move(void *location, Direction direction);

最初の関数は何も返しません。2番目の関数はvoidポインターを返します。私はその2番目の関数を次のように宣言していました

void* move(void* location, Direction direction);

「void」を「意味なし」を意味すると考える場合に役立つかもしれません。の戻り値describeは「意味なし」です。このような関数から値を返すことは、戻り値に意味がないことをコンパイラに伝えたため、違法です。意味がないため、その関数からの戻り値をキャプチャすることはできません。型の変数はvoid意味がないため宣言できません。たとえば、void nonsense;ナンセンスであり、実際には違法です。また、voidの配列void void_array[42];もナンセンスであることに注意してください。

void(void*)へのポインターは別のものです。配列ではなくポインタです。void*「意味のない」何かを指すポインターを意味するものとして読んでください。ポインターは「意味なし」です。つまり、そのようなポインターを逆参照することは意味がありません。実際にそうすることは違法です。voidポインターを逆参照するコードはコンパイルされません。

したがって、voidポインターを逆参照できず、voidの配列を作成できない場合、どのように使用できますか?答えは、任意の型へのポインタをキャストできることvoid*です。void*元の型へのポインターをキャストし、元の型へのポインターにキャストすると、元のポインターと等しい値が生成されます。C標準はこの動作を保証します。Cで非常に多くのvoidポインターが表示される主な理由は、この機能が非常に多くの非オブジェクト指向言語で情報隠蔽とオブジェクトベースのプログラミングを実装する方法を提供することです。

最後に、その理由は、あなたは、多くの場合、参照moveとして宣言されたvoid *move(...)のではなくvoid* move(...)、むしろタイプよりも、名前の横にアスタリスクを置くことはC.では非常に一般的な方法であるため、その理由は、次の宣言は大きなトラブルにあなたを取得することです。int* iptr, jptr;これは、あなたになるだろうあなたはへの2つのポインタを宣言していると思いますint。あなたは違う。この宣言はjptrintへのポインタではなくを作成しintます。適切な宣言は次int *iptr, *jptr;のとおりです。宣言ステートメントごとに1つの変数のみを宣言するという慣行に固執する場合、アスタリスクが型に属するふりをすることができます。しかし、あなたはふりをしていることに留意してください。


「ヌルポインターを逆参照するコードはコンパイルされません。」voidポインターを逆参照するコードはコンパイルされないということだと思います。コンパイラーは、nullポインターの逆参照からユーザーを保護しません。それはランタイムの問題です。
コーディグレー14

@CodyGray-ありがとう!それを修正しました。NULLポインターを逆参照するコードはコンパイルされます(ポインターがそうでない場合void* null_ptr = 0;)。
デビッドハメン14

2

簡単に言えば、違いはありません。例を挙げましょう。

Carというクラスがあるとします。

Car obj = anotherCar; // obj is now of type Car
Car* obj = new Car(); // obj is now a pointer of type Car
void obj = 0; // obj has no type
void* = new Car(); // obj is a pointer with no type

私はここで混乱をあなたはどのように定義するかオフに基づいており、ここで信じているvoidvoid*。return-type voidは「何も返さない」ことを意味し、return-type は「何も返さない」ことをvoid*意味します。

これは、ポインターが特殊なケースであるという事実によるものです。ポインターオブジェクトは、有効なオブジェクトがあるかどうかにかかわらず、常にメモリ内の場所を指します。私のかどうかvoid* carの言及バイト、ワード、車、または自転車の問題ではない私のためvoid*にポイント何かがない定義された型を持ちます。後で定義するかどうかは私たち次第です。

C#やJavaなどの言語では、メモリが管理され、ポインターの概念がObjectクラスにロールオーバーされます。「nothing」タイプのオブジェクトへの参照を持つ代わりに、少なくともオブジェクトが「Object」タイプであることを知っています。理由を説明します。

従来のC / C ++では、オブジェクトを作成してそのポインターを削除すると、そのオブジェクトにアクセスできなくなります。これは、メモリリークと呼ばれるものです。

C#/ Javaでは、すべてがオブジェクトであるため、ランタイムはすべてのオブジェクトを追跡できます。私のオブジェクトがCarであることを必ずしも知る必要はありません。Objectクラスによってすでに処理されている基本的な情報を知っている必要があります。

プログラマにとって、C / C ++で手動で処理する必要がある情報の多くはObjectクラスによって処理され、ポインタである特殊なケースの必要性がなくなります。


「私のvoid *は、定義されたタイプのないものを指します」-正確ではありません。長さがゼロの値へのポインタであると言う方が正しいです(ほとんど要素が0の配列に似ていますが、配列に関連付けられた型情報もありません)。
スコットホイットロック14

2

これらのことをCでどのように考えることができるかを言おうと思います。C標準の公式言語はで本当に楽ではないのでvoid、私はそれと完全に一致しようとはしません。

このタイプvoidは、タイプの中で第一級の市民ではありません。オブジェクト型ですが、オブジェクト(または値)、フィールド、または関数パラメーターの型にすることはできません。ただし、関数の戻り値型である可能性があり、それによって式の型になる可能性があります(基本的にそのような関数の呼び出しですが、条件演算子で形成された式はvoid型を持つこともできます)。しかし、であっても、最小限の使用void値の型としてそのつまり返す関数終了、のために開い上記の葉ドアvoidの形の文で型の式で、明示的に(それはかかわらず、C ++で許可されているCで禁止されているが、ただし、C)には該当しない理由のため。return E;Evoid

タイプのオブジェクトvoidが許可された場合、それらは0ビット(つまり、voidタイプの式の値の情報量)を持ちます。そのようなオブジェクトを許可することは大きな問題ではありませんが、それらはかなり役に立たないでしょう(サイズ0のオブジェクトはポインター演算を定義するのに多少の困難を与えます。このようなオブジェクトのような異なる値のセットには、1つの(簡単な)要素があります。それは物質を持たないため、その値を取ることができますが、変更することはできません(異なる値を欠いてます)。したがって、標準ではvoid、空の値のセットで構成されると混同されます。そのような型は、できない式を記述するのに役立ちますC言語は実際にそのようなタイプを使用していませんが、評価される(ジャンプや非終了呼び出しなど)。

値型として許可されvoidていないため、「禁止」を指定するために直接関係のない少なくとも2つの方法で使用されてい(void)ます:関数型でパラメーター指定として指定することは、引数への引数の提供が禁止されていることを意味します(逆に「()」はすべてを意味します)許可されており、はい、パラメーター指定のvoid関数への引数として式を提供することさえ(void)禁止されています)、宣言void *pは、参照解除式*pが禁止されていることを意味します(ただし、それがtypeの有効な式であることではありませんvoid)。したがって、これらの使用は、有効なタイプの式としてvoid実際には一貫しvoidていないことは正しいです。ただし、タイプのオブジェクトvoid*実際には、どのタイプの値へのポインターでもありません。逆参照することはできませんが、ポインターとして処理される値を意味し、それを使用したポインター演算は禁止されています。「ポインタとして扱われる」とは、情報を失うことなく、あらゆるポインタタイプからキャストできることを意味します。ただし、整数値をキャストするために使用することもできるため、何も指す必要はありません。


厳密に言えば、それは鋳造することができるからとに常にではない情報を失うことなく、任意のオブジェクトのポインタ型が、にしてから。一般的なAキャストでvoid*の値があれば情報が失われる可能性があります別のポインタ型に(UBを持っているかさえ、私は手をオフに思い出すことができない)、しかし、void*あなたが今にキャストしている同じ型のポインタをキャストから来た、その後、そうではありません。
スティーブジェソップ

0

C#およびJavaでは、すべてのクラスはObjectクラスから派生します。したがって、「何か」への参照を渡したいときはいつでも、typeの参照を使用できますObject

これらの言語でvoidは、その目的のためにvoidポインターを使用する必要がないため、ここで「何か」を意味する必要はありません。


0

Cでは、あらゆるタイプのものへのポインターを持つことが可能です[ポインターは参照のタイプとして動作します]。ポインターは、既知のタイプのオブジェクトを識別する場合もあれば、任意の(不明な)タイプのオブジェクトを識別する場合もあります。Cの作成者は、「特定のタイプのオブジェクトへのポインター」と同じ「任意のタイプのオブジェクトへのポインター」に同じ一般的な構文を使用することを選択しました。後者の場合の構文は、タイプが何であるかを指定するためにトークンを必要とするため、前者の構文は、タイプがわかっている場合、そのトークンが属する場所に何かを置く必要があります。Cは、そのためにキーワード「void」を使用することを選択しました。

Pascalでは、Cと同様に、あらゆるタイプのものへのポインターを使用できます。Pascalのより一般的な方言では、「任意の型のものへのポインター」も使用できますが、「特定の型のものへのポインター」に使用するのと同じ構文を使用するのではなく、単に前者をとして参照しPointerます。

Javaには、ユーザー定義の変数タイプはありません。すべてがプリミティブまたはヒープオブジェクト参照です。「特定のタイプのヒープオブジェクトへの参照」または「任意のタイプのヒープオブジェクトへの参照」というタイプのものを定義できます。前者は、特別な句読点を付けずに型名を使用して、それが参照であることを示します(他に何もできないため)。後者はタイプ名を使用しObjectます。

void*任意のタイプの何かへのポインタの命名法としての使用は、私がCおよびC ++のような直接派生物に固有である限り知っています。私が知っている他の言語は、明確に名前が付けられた型を使用するだけで概念を処理します。


-1

キーワードをvoid空のように考えることができますstructC99では空の構造体は明らかに許可されていません..NETおよびC ++では0以上のメモリを占有し、最後にJavaのすべてがクラスであることを思い出します):

struct void {
};

...これは、長さが0のタイプであることを意味します。これはvoid、整数の戻り値のスタックに4バイト程度を割り当てる代わりに... を返す関数呼び出しを行うときの動作方法です。スタックポインターはまったく変更されません(長さ0ずつ増加します) )。

したがって、次のような変数を宣言した場合:

int a;
void b;
int c;

...そして、あなたはおそらく、その後、それらの変数のアドレスを取ったbcするので、メモリ内の同じ場所に設置されることがありますbゼロの長さを有しています。したがって、a void*は、長さがゼロの組み込み型へのメモリ内のポインタです。すぐ後にあなたにとって役立つかもしれない何かがあることを知っているかもしれないという事実は、別の問題であり、あなたが何をしているかを本当に知る必要があります(そして危険です)。


私はその割り当てのvoid型にする長さを知っている唯一のコンパイラはGCC、とgccが割り当てること1の長さである
ジョシュア
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.