C#リファレンスとポインターの違いは何ですか?


86

C#参照とポインターの違いがよくわかりません。どちらも記憶の中の場所を指していますね。私が理解できる唯一の違いは、ポインターはそれほど賢くなく、ヒープ上の何も指すことができず、ガベージコレクションが免除され、構造体または基本型のみを参照できることです。

私が尋ねる理由の1つは、優れたプログラマーになるには、人々がポインターをよく理解する必要があるという認識があることです(Cからだと思います)。高水準言語を学ぶ多くの人々はこれを見逃しているため、この弱点を持っています。

ポインタについてそれほど複雑なものがわかりませんか?それは基本的には記憶の中の場所への単なる参照ですよね?その場所を返し、その場所のオブジェクトと直接対話できますか?

私は大きなポイントを逃したことがありますか?


1
簡単な答えは「はい」です。あなたはかなり重要な何かを見逃しました。それが「...人々がポインタを理解する必要があるという認識」の理由です。ヒント:C#だけが言語ではありません。
jdigital 2009年

回答:


51

C#参照は、ガベージコレクターによって再配置できますが、通常のポインターは静的です。これがfixed、配列要素へのポインタを取得するときにキーワードを使用して、配列要素が移動しないようにする理由です。

編集:概念的には、はい。それらは多かれ少なかれ同じです。


C#参照が参照のオブジェクトをGCによって移動するのを防ぐ別のコマンドはありませんか?
リチャード

申し訳ありませんが、投稿がポインタを参照しているため、別のものだと思いました。
リチャード

はい、GCHandle.Alloc、またはMarshal.AllocHGlobal(修正済みを超えています)
2009年

これは、C#、C ++ / CLIでpin_ptrに固定しています
MMX

Marshal.AllocHGlobalは、マネージヒープにメモリをまったく割り当てず、当然、ガベージコレクションの対象にはなりません。
mmx 2009年

133

ポインタと参照の間には、わずかですが非常に重要な違いがあります。ポインタはメモリ内の場所を指し、参照はメモリ内のオブジェクトを指します。ポインタは、ポインタが指すメモリの正確さを保証できないという意味で「タイプセーフ」ではありません。

次のコードを例にとってみましょう

int* p1 = GetAPointer();

GetAPointerがint *と互換性のある型を返す必要があるという意味で、これは型セーフです。それでも、* p1が実際にintを指すという保証はありません。これは、char、double、またはランダムメモリへの単なるポインタである可能性があります。

ただし、参照は特定のオブジェクトを指します。オブジェクトはメモリ内で移動できますが、参照を無効にすることはできません(安全でないコードを使用しない限り)。この点で、参照はポインタよりもはるかに安全です。

string str = GetAString();

この場合、strは2つの状態のいずれかを持ちます。1)オブジェクトを指していないためnullであるか、2)有効な文字列を指しています。それでおしまい。CLRは、これが事実であることを保証します。ポインタを使用することはできません。


13

参照は「抽象的な」ポインタです。参照を使用して算術演算を実行したり、その値を使用して低レベルのトリックを実行したりすることはできません。


8

参照とポインターの主な違いは、ポインターはビットのコレクションであり、その内容はポインターとしてアクティブに使用されている場合にのみ重要ですが、参照はビットのセットだけでなく、その存在を知らされた基礎となるフレームワーク。メモリ内のあるオブジェクトへのポインタが存在し、そのオブジェクトが削除されてもポインタが消去されない場合、ポインタが指すメモリにアクセスしようとしない限り、またはポインタが存在し続けることは害を及ぼしません。ポインタを使用しようとしない場合、その存在については何も気にしません。対照的に、.NETやJVMのような参照ベースのフレームワークでは、システムが存在するすべてのオブジェクト参照を常に識別できる必要があり、存在するすべてのオブジェクト参照は常に次のいずれかである必要があります。nullまたは、適切なタイプのオブジェクトを識別します。

各オブジェクト参照は、実際には2種類の情報をカプセル化することに注意してください。(1)それが識別するオブジェクトのフィールドコンテンツ、および(2)同じオブジェクトへの他の参照のセットです。システムがオブジェクトに存在するすべての参照をすばやく識別できるメカニズムはありませんが、オブジェクトに存在する他の参照のセットは、参照によってカプセル化される最も重要なものであることがよくあります(これは特に次の場合に当てはまります)。タイプのObjectものは、ロックトークンのようなものとして使用されます)。システムは、で使用するためにオブジェクトごとに数ビットのデータを保持しますがGetHashCode、オブジェクトには、それらに存在する一連の参照以外に実際のIDはありません。場合Xオブジェクトへの現存する唯一の参照を保持しているは、X同じフィールドの内容を持つ新しいオブジェクトへの参照では、によって返されるビットを変更することを除いて、識別可能なGetHashCode()、そしてその効果でさえ保証されていません。


5

ポインタは、メモリアドレス空間内の場所を指します。参照はデータ構造を指します。データ構造はすべて、ガベージコレクター(メモリスペースを圧縮するため)によって常に(それほど頻繁ではありませんが、時々)移動しました。また、あなたが言ったように、参照のないデータ構造はしばらくするとガベージコレクションされます。

また、ポインタは安全でない状況でのみ使用できます。


5

開発者にとって、ポインタの概念を理解すること、つまり間接参照を理解することが重要だと思います。それは彼らが必ずしもポインタを使わなければならないという意味ではありません。これは、ということを理解することも重要です参照の概念から異なり、ポインタの概念を唯一の微妙ものの、しかし、リファレンスの実装では、ほとんど常にということですポインタ。

つまり、参照を保持する変数は、オブジェクトへのポインタを保持するポインタサイズのメモリブロックにすぎません。ただし、この変数は、ポインター変数を使用できるのと同じ方法で使用することはできません。C#(およびC、C ++、...)では、ポインターは配列のようにインデックスを付けることができますが、参照はできません。C#では、参照はガベージコレクターによって追跡されますが、ポインターは追跡できません。C ++では、ポインタを再割り当てできますが、参照は再割り当てできません。構文的にも意味的にも、ポインターと参照はまったく異なりますが、機械的には同じです。


配列のことは興味深いように聞こえますが、基本的には、参照を使用してこれを行うことができないときに、配列のようにメモリ位置をオフセットするようにポインタに指示できるということですか?それがいつ役立つかは考えられませんが、それでも興味深いものです。
リチャード

pがint *(intへのポインタ)の場合、(p + 1)はp + 4バイト(intのサイズ)で識別されるアドレスです。また、p [1]は*(p + 1)と同じです(つまり、pの4バイト先のアドレスを「逆参照」します)。対照的に、配列参照(C#)では、[]演算子は関数呼び出しを実行します。
P Daddy

5

まず、セマティックで「ポインタ」を定義する必要があると思います。固定された安全でないコードで作成できるポインタを意味しますか?おそらくネイティブコールまたはMarshal.AllocHGlobalから取得するIntPtrを意味しますか?GCHandleのことですか?クラス、数値、構造体など、すべてが本質的に同じものであり、何かが格納されているメモリアドレスの表現です。そして、記録のために、それらは確かにヒープ上にある可能性があります。

ポインタ(上記のすべてのバージョン)は固定アイテムです。GCはそのアドレスに何があるのか​​わからないため、オブジェクトのメモリや寿命を管理することはできません。つまり、ガベージコレクションシステムのすべての利点が失われます。オブジェクトメモリを手動で管理する必要があり、リークが発生する可能性があります。

一方、参照は、GCが知っているほとんどの「マネージドポインタ」です。それはまだオブジェクトのアドレスですが、GCはターゲットの詳細を知っているので、ターゲットを移動したり、圧縮したり、ファイナライズしたり、破棄したり、管理された環境で行うその他すべての優れた機能を実行できます。

主な違いは、実際には、それらを使用する方法と理由にあります。管理言語のほとんどの場合、オブジェクト参照を使用します。ポインタは、相互運用を行うのに便利であり、非常に高速な作業が必要になることはめったにありません。

編集:実際、マネージコードで「ポインター」を使用する場合の良い例を次に示します。この場合はGCHandleですが、AllocHGlobalを使用するか、バイト配列または構造体でfixedを使用することでまったく同じことができます。GCHandleは、私にとってより「.NET」であると感じるため、私はGCHandleを好む傾向があります。


イリノイ州のオブジェクト参照とはまったく異なるものであるため、ここで「マネージドポインタ」と言ってはいけないというちょっとした疑問があります。C ++ / CLIにはマネージポインターの構文がありますが、通常はC#からアクセスできません。ILでは、それらは(ie)ldlocaおよびldarga命令で取得されます。
Glenn Slayden 2011

5

ポインタは、アプリケーションのアドレス空間内の任意のバイトを指すことができます。参照は、.NET環境によって厳しく制限され、制御および管理されます。


1

それらをいくらか複雑にするポインタについてのことは、それらが何であるかではなく、それらを使って何ができるかです。そして、あなたがポインタへのポインタを持っているとき。それが本当に楽しくなり始める時です。


1

ポインタに対する参照の最大の利点の1つは、単純さと読みやすさの向上です。いつものように、何かを単純化すると使いやすくなりますが、柔軟性と制御が犠牲になりますが、低レベルのものを使用できます(他の人が述べているように)。

ポインタはしばしば「醜い」と批判されます。

class* myClass = new class();

これで、使用するたびに、最初に次のいずれかで逆参照する必要があります。

myClass->Method() or (*myClass).Method()

読みやすさが失われ、複雑さが増すにもかかわらず、(値を渡す代わりに)実際のオブジェクトを変更し、巨大なオブジェクトをコピーする必要がないというパフォーマンスの向上のために、パラメーターとしてポインターを頻繁に使用する必要がありました。

私にとって、これが、ポインタと同じ利点を提供するために参照が最初に「生まれた」理由ですが、そのすべてのポインタ構文はありません。これで、(値だけでなく)実際のオブジェクトを渡すことができ、オブジェクトを操作するためのより読みやすい通常の方法が得られます。

MyMethod(&type parameter)
{
   parameter.DoThis()
   parameter.DoThat()
}

C ++参照は、C#/ Java参照とは異なり、一度値を割り当てると再割り当てできません(宣言時に割り当てる必要があります)。これは、constポインター(別のオブジェクトに再ポイントできないポインター)を使用するのと同じでした。

JavaとC#は非常に高レベルの現代言語であり、長年にわたってC / C ++に蓄積された多くの混乱をクリーンアップし、ポインターは間違いなく「クリーンアップ」する必要のあるものの1つでした。

ポインターを知ることについてのあなたのコメントがあなたをより強力なプログラマーにする限り、これはほとんどの場合真実です。知らないうちにそれを使用するのではなく、何かが「どのように」機能するかを知っているなら、これはしばしばあなたに優位性を与えることができると思います。エッジの量は常に変化します。結局のところ、それがどのように実装されているかを知らずに何かを使用することは、OOPとインターフェースの多くの美しさの1つです。

この特定の例では、ポインターについて知っていると、参照に役立ちますか?C#参照はオブジェクト自体ではなく、オブジェクトを指していることを理解することは、非常に重要な概念です。

#1:値を渡していない 初心者にとっては、ポインターを使用すると、ポインターがアドレスだけを保持していることがわかります。それだけです。変数自体はほとんど空であるため、引数として渡すと非常に便利です。パフォーマンスの向上に加えて、実際のオブジェクトを操作しているため、行った変更は一時的なものではありません。

#2:ポリモーフィズム/インターフェース インターフェイスタイプの参照があり、それがオブジェクトを指している場合、オブジェクトにさらに多くの機能がある場合でも、そのインターフェイスのメソッドのみを呼び出すことができます。オブジェクトは、同じメソッドを異なる方法で実装する場合もあります。

これらの概念をよく理解していれば、ポインターを使用しなかったことで多くのことを見逃しているとは思いません。C ++は、時々手を汚すのが良いので、プログラミングを学ぶための言語としてよく使われます。また、低レベルの側面で作業することで、現代語の快適さを理解できます。私はC ++から始めて、現在はC#プログラマーです。生のポインターを使用することで、内部で何が起こっているのかをよりよく理解できるようになりました。

誰もがポインタから始める必要はないと思いますが、重要なのは、値型の代わりに参照が使用される理由を理解していることです。それを理解する最良の方法は、その祖先であるポインタを調べることです。


1
個人的に、私はC#を使用がいることをほとんどの場所であれば、より良い言語だったと思います.使用し->ますが、foo.bar(123)静的メソッドの呼び出しと同義でしたfooClass.bar(ref foo, 123)。それは次のようなことを可能にしたでしょうmyString.Append("George"); [これは変更となる変数を myString]との間の意味でより多くの明白な違いをしたmyStruct.field = 3;myClassObject->field = 3;
スーパーキャット2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.