別名 - ポインターに対するこの強迫観念は何ですか?
ActionScript、Java、C# などの最新のオブジェクト指向言語しか実際に使用していないので、ポインターの重要性とポインターの使用目的がよくわかりません。ここで何が欠けていますか?
それはすべて単なる間接的なものです: データを扱わずに、「そこにあるデータに案内します」と言う能力。Java と C# でも同じ概念がありますが、参照形式のみです。
主な違いは、参照は事実上不変の道しるべであることです。参照は常に何かを指しています。これは便利で理解しやすいですが、C ポインター モデルよりも柔軟性が低くなります。C ポインターは、喜んで書き直すことができる道しるべです。探している文字列が指している文字列の隣にあることを知っていますか? さて、道標を少しだけ変更します。
これは、C の「骨に近い、低レベルの知識が必要」というアプローチとうまく結合します。aは、foo 標識が指す位置から始まる一連の文字で構成されていることがわかっています。char* foo
文字列の長さが 10 文字以上であることがわかっている場合は、標識を に変更して(foo + 5)
同じ文字列を指すようにできますが、長さの半分から始めます。
この柔軟性は、自分が何をしているのかを知っている場合に役立ちます。そうでない場合は死にます (「知っている」とは、単に「言語を知っている」以上のものであり、「プログラムの正確な状態を知っている」ことです)。道を間違えると、道しるべが崖の端からあなたを誘導しています。参照をいじることができないので、リスクなしで参照できるという自信が大幅に高まります (特に、ほとんどのガベージ コレクション言語のように、「参照されたオブジェクトは決して消えない」などのルールと組み合わせた場合)。
あなたはたくさんを逃しています!コンピュータが低レベルでどのように機能するかを理解することは、いくつかの状況で非常に役立ちます。Cとアセンブラがそれを行います。
基本的に、ポインタを使用すると、コンピュータのメモリ内の任意の場所にデータを書き込むことができます。より原始的なハードウェア/OSまたは組み込みシステムでは、これは実際に何か有用なことをするかもしれません。もう一度blinkenlichtsのオンとオフを切り替えてください。
もちろん、これは最新のシステムでは機能しません。オペレーティングシステムは、メインメモリの主でありマスターです。間違ったメモリの場所にアクセスしようとすると、プロセスはその傲慢さの代償を払います。
Cでは、ポインターはデータへの参照を渡す方法です。関数を呼び出すとき、スタックに100万ビットをコピーする必要はありません。代わりに、データがメインメモリのどこにあるかを伝えるだけです。つまり、データへのポインタを指定します。
ある程度、それはJavaでも起こります。オブジェクト自体ではなく、オブジェクトへの参照を渡します。最終的には、すべてのオブジェクトがコンピュータのメインメモリ内のビットのセットであることを忘れないでください。
ポインタは、メモリの内容を直接操作するためのものです。
これが良いことだと思うかどうかはあなた次第ですが、これは C やアセンブラーで何かを行うための基本です。
高水準言語はポインターを舞台裏に隠します。たとえば、Java の参照は、遭遇するほとんどすべての JVM でポインターとして実装されます。これが、NullReferenceException ではなく NullPointerException と呼ばれる理由です。しかし、プログラマがそれが指すメモリアドレスに直接アクセスすることはできず、正しい型のオブジェクトのアドレス以外の値を取るように変更することはできません。したがって、低水準言語のポインターと同じ力 (および責任) は提供されません。
[編集: これは、「このポインターへの執着は何ですか?」という質問に対する回答です。私が比較したのは、アセンブラー/C スタイルのポインターと Java 参照だけです。その後、質問のタイトルが変更されました。新しい質問に答えようとしたら、Java 以外の言語の参照について言及したかもしれません]
これは、「CPU命令に対するこの執着は何ですか?」と尋ねるようなものです。x86 MOV命令をあちこちに散らかさないことで、何かを見逃していませんか?」</ p>
低レベルでプログラミングする場合は、ポインタが必要です。ほとんどの高級プログラミング言語の実装では、ポインターはCと同じように広範囲に使用されますが、コンパイラーによってユーザーから隠されます。
だから...心配しないでください。あなたはすでにポインタを使用しています-そしてそれを誤って使用する危険もありません。:)
私はポインタを車のマニュアルトランスミッションと見ています。オートマチック トランスミッションを搭載した車で運転することを学べば、運転が下手になることはありません。そして、マニュアルトランスミッションで学んだドライバーができることのほとんどすべてを行うことができます。運転の知識に穴が開くだけです。マニュアルを運転しなければならない場合、おそらく問題が発生するでしょう。確かに、その基本的なコンセプトを理解するのは簡単ですが、ヒルスタートをしなければならない場合は、めちゃくちゃになります. しかし、マニュアルトランスミッションの場所はまだあります。たとえば、レースカーのドライバーは、現在のレース状況に最適な方法で車を応答させるためにシフトできる必要があります。マニュアルトランスミッションを持つことは、彼らの成功にとって非常に重要です。
これは現在のプログラミングと非常によく似ています。一部のソフトウェアでは C/C++ 開発が必要です。いくつかの例としては、ハイエンドの 3D ゲーム、低レベルの組み込みソフトウェア、速度がソフトウェアの目的の重要な部分であり、処理が必要な実際のデータにより近いアクセスを可能にする低レベル言語がそのパフォーマンスの鍵となります。 . ただし、ほとんどのプログラマーにとってはそうではなく、ポインターを知らなくても問題はありません。ただし、C とポインター、および手動送信について学ぶことで、誰もが恩恵を受けることができると私は信じています。
あなたはオブジェクト指向言語でプログラミングをしてきたので、このように言いましょう。
オブジェクト A がオブジェクト B をインスタンス化し、それをメソッド パラメーターとしてオブジェクト C に渡します。オブジェクト C は、オブジェクト B の一部の値を変更します。オブジェクト A のコードに戻ると、オブジェクト B の変更された値を確認できます。これはなぜですか?
オブジェクト B の参照をオブジェクト C に渡したので、オブジェクト B の別のコピーは作成されませんでした。そのため、オブジェクト A とオブジェクト C はどちらも同じオブジェクト B への参照をメモリ内に保持しています。ある場所から変化し、別の場所で見られます。これは、参照によって呼び出されます。
ここで、代わりに int や float などのプリミティブ型を使用し、それらをメソッド パラメーターとして渡すと、オブジェクト Aは変数の独自のコピーの参照ではなくコピーを渡しただけなので、オブジェクト C の変更はオブジェクト A には表示されません。. これは値によると呼ばれます。
あなたはおそらくそれをすでに知っていました。
C 言語に戻ると、関数 A は関数 B にいくつかの変数を渡します。これらの関数パラメーターは、値によってネイティブにコピーされます。関数 B が関数 A に属するコピーを操作するには、関数 A が参照渡しになるように、変数へのポインターを渡す必要があります。
「ねえ、これが私の整数変数へのメモリアドレスです。新しい値をそのアドレスの場所に置いてください。後で取得します。」
概念は似ていますが、100% 類似しているわけではないことに注意してください。ポインターは、単に「参照渡し」で渡すだけではありません。ポインターを使用すると、関数はメモリの任意の場所を必要な値に操作できます。ポインターは、データ変数だけでなく、任意のロジックを動的に実行する実行コードの新しいアドレスを指すためにも使用されます。ポインターは他のポインターを指す場合もあります(ダブルポインター)。これは強力ですが、検出が難しいバグやセキュリティの脆弱性を非常に簡単に導入することもできます.
私は日々の仕事でポインタと参照を頻繁に使用しています...マネージ コード (C#、Java) とアンマネージ コード (C++、C) で。ポインターの扱い方とポインターが何であるかについては、マスター自身から学びました... [Binky!!][1] 他に言う必要はありません ;)
ポインタと参照の違いはこれです。ポインタは、メモリのブロックへのアドレスです。書き換えることができます。つまり、別のメモリ ブロックに再割り当てできます。参照は単にオブジェクトの名前を変更することです。割り当てられるのは 1 回だけです。オブジェクトに割り当てられると、別のオブジェクトに割り当てることはできません。参照はアドレスではなく、変数の別の名前です。詳細については、 C++ FAQを参照してください。
以前にポインターを見たことがない場合は、この小さな宝石を見逃しているに違いありません。
void strcpy(char *dest, char *src)
{
while(*dest++ = *src++);
}
歴史的に、プログラミングを可能にしたのは、データだけでなくコンピューターの命令を記憶場所に保持できるという認識でした。
ポインタは、メモリ位置が他のメモリ位置のアドレスも保持できるという認識から生じたものであり、したがって間接性を与えます。(低レベルでの) ポインターがなければ、最も複雑なデータ構造は不可能です。リンク リスト、バイナリ ツリー、またはハッシュ テーブルはありません。参照渡しではなく、値渡しのみです。ポインターはコードを指すことができるため、それらがなければ、仮想関数や関数ルックアップ テーブルもありません。
私は現在、データのチャンク (この場合は SQL データベースに格納されている) が 1 つ以上の他のエンティティによって参照される高レベルのエンタープライズ ソフトウェアの設計に深く取り組んでいます。参照するエンティティがなくなったときにデータのチャンクが残っている場合は、ストレージを浪費しています。参照が存在しないデータを指している場合、それも大きな問題です。
私たちの問題と、ポインタを使用する言語でのメモリ管理の問題との間には、強い類推があります。そのアナロジーの観点から同僚と話すことができることは、非常に役に立ちます。参照されていないデータを削除しないことは、「メモリ リーク」です。どこにも行かない参照は「ダングリング ポインター」です。明示的な「解放」を選択するか、「参照カウント」を使用して「ガベージ コレクション」を実装できます。
ここで、低レベルのメモリ管理を理解することは、高レベルのアプリケーションの設計に役立ちます。
Java では、常にポインターを使用しています。ほとんどの変数はオブジェクトへのポインターです。その理由は次のとおりです。
StringBuffer x = new StringBuffer("Hello");
StringBuffer y = x;
x.append(" boys");
System.out.println(y);
... "Hello" ではなく "Hello boys" を出力します。
C での唯一の違いは、ポインターの加算と減算が一般的であることです。ロジックを間違えると、触れてはいけないデータをいじってしまう可能性があります。
文字列は C (およびその他の関連言語) の基本です。C でプログラミングする場合、メモリを管理する必要があります。「わかりました、たくさんの文字列が必要です」と言うだけではありません。データ構造を考える必要があります。どのくらいのメモリが必要ですか? いつ配るの?いつ解放するの?それぞれが 80 文字以下の 10 個の文字列が必要だとします。
さて、各文字列は文字の配列 (81 文字 - null を忘れてはいけません。そうしないと、申し訳ありません!) であり、各文字列自体が配列内にあります。最終結果は、次のような多次元配列になります
char dict[10][81];
ちなみに、dict は「文字列」でも「配列」でも「文字」でもないことに注意してください。ポインターです。これらの文字列の 1 つを出力しようとすると、1 文字のアドレスを渡すだけです。C は、文字の出力を開始しただけでは、最終的に null にヒットすると想定しています。また、1 つの文字列の先頭にいて、81 バイト前方にジャンプすると、次の文字列の先頭にいると見なされます。実際、ポインタを取得して 81 バイトを追加することが、次の文字列にジャンプする唯一の方法です。
では、なぜポインターが重要なのでしょうか? 彼らなしでは何もできないからです。一連の文字列を出力するような単純なことさえできません。リンクリスト、ハッシュ、キュー、ツリー、ファイルシステム、メモリ管理コード、カーネルなどの実装など、興味深いことは何もできません。C はメモリのブロックを渡すだけで、あとは自分でやらせてくれるので、それらを理解する必要があります。生のメモリのブロックを使って何かを行うにはポインタが必要です。
また、多くの人が、ポインターを理解する能力はプログラミング スキルと高い相関があると示唆しています。ジョエルは、とりわけ、この議論をしました。例えば
さて、現在書かれているコードの 90% ではポインタを使ったプログラミングは必要ないことを率直に認めます。実際、実稼働コードではまったく危険です。わかった。それはいいです。また、関数型プログラミングは実際にはあまり使用されていません。同意した。
しかし、最もエキサイティングなプログラミングの仕事のいくつかにとって、それは依然として重要です。たとえば、ポインターがなければ、Linux カーネルで作業することはできません。ポインターを本当に理解していなければ、Linux のコード行を理解することはできません。
ここから。優れた記事。
正直なところ、経験豊富な開発者のほとんどは、ヒントを知らなくても笑ってしまいます (友好的であることが望ましいです)。私の前職では、昨年新入社員(卒業したばかり)が2人いて、ポインターのことを知らず、それだけで1週間ほど話題になりました。ポインタを知らずに卒業できるなんて誰も信じられなかった...
私は長い間ポインターを理解していませんでしたが、配列のアドレス指定は理解していました。したがって、私は通常、オブジェクト用のストレージ領域を配列にまとめ、その配列へのインデックスを「ポインター」の概念として使用します。
SomeObject store[100];
int a_ptr = 20;
SomeObject A = store[a_ptr];
このアプローチの問題点の 1 つは、'A' を変更した後、変更を永続的にするには、それを 'store' 配列に再割り当てする必要があることです。
store[a_ptr] = A;
舞台裏では、プログラミング言語がいくつかのコピー操作を行っていました。ほとんどの場合、これはパフォーマンスに影響しませんでした。ほとんどの場合、コードはエラーが発生しやすく、反復的になりました。
ポインターを理解することを学んだ後、私は配列アドレス指定アプローチの実装から離れました。類推はまだかなり有効です。「ストア」配列がプログラミング言語のランタイムによって管理されることを考慮してください。
SomeObject A;
SomeObject* a_ptr = &A;
// Any changes to a_ptr's contents hereafter will affect
// the one-true-object that it addresses. No need to reassign.
最近では、オブジェクトを正当にコピーできない場合にのみポインターを使用します。これが当てはまる理由はいくつかあります。
また、コードのブロックを安全でないとマークすることで、C# で (通常の参照とは対照的に) ポインターを使用できることに注意してください。次に、メモリアドレスを直接変更して、ポインター演算などの楽しいことを実行できます。非常に高速な画像操作に最適です (私が個人的に使用した唯一の場所です)。
私の知る限り、Java と ActionScript は安全でないコードとポインタをサポートしていません。
ポインターは、下位レベルのプログラミング言語で間接参照を表す最も実用的な方法です。
ポインタは重要です!それらはメモリアドレスを「指し」、多くの内部構造はポインターとして表されます.IE、文字列の配列は実際にはポインターへのポインターのリストです! ポインターは、関数に渡される変数の更新にも使用できます。
スタックにメモリを事前に割り当てることなく、実行時に「オブジェクト」を生成する場合に必要です。
パラメーターの効率 - (任意に大きい) オブジェクト全体をコピーするのではなく、ポインター (Int - 4 バイト) を渡します。
Java クラスは参照 (基本的にはポインター) を介して渡されますが、プログラマーから隠されているのは Java だけです。
C や C++ などの言語でプログラミングすると、「金属」にかなり近づきます。ポインターは、変数、データ、関数などが存在するメモリの場所を保持します。値渡しの代わりにポインターを渡すことができます (変数とデータをコピーします)。
ポインターで難しいことが 2 つあります。
ポインタの動作を Java オブジェクトの受け渡し方法と比較できますが、Java ではガベージ コレクションによって処理されるため、メモリの解放について心配する必要はありません。このようにして、ポインターの利点を得ることができますが、マイナス面に対処する必要はありません。もちろん、オブジェクトを逆参照しないと、Java でメモリ リークが発生する可能性がありますが、それは別の問題です。
c または c++ を 2 行以上書いてみればわかります。
それらは、変数のメモリ位置への「ポインタ」です。参照によって変数を渡すようなものです。