ポインターを理解するための障壁は何ですか?それらを克服するために何ができるでしょうか?[閉まっている]


449

ポインターが、CまたはC ++の多くの新しい、さらには古い、大学生レベルの学生にとって混乱の主な要因であるのはなぜですか?ポインターが変数、関数、およびレベルを超えてどのように機能するかを理解するのに役立つツールまたは思考プロセスはありますか?

誰かが「ああ、わかった」のレベルに到達するために、全体的なコンセプトに夢中にならないようにするために実行できるいくつかの優れた実践方法は何ですか?基本的に、シナリオのようにドリルします。


20
この質問の論文は、ポインターを理解するのが難しいということです。この質問は、ポインタが他の何よりも理解するのが難しいという証拠を提供していません。
bmargulies

14
たぶん、GCCで記述された言語でコーディングしているために何かが足りないかもしれませんが、メモリ内のポインタがKey-> Value構造体であるかどうかを常に考えていました。プログラム内で大量のデータを渡すのはコストがかかるため、構造(値)を作成し、そのポインター/参照(キー)を渡すのは、キーが大きな構造のはるかに小さな表現であるためです。難しいのは、2つのポインター/参照を比較する必要がある場合(キーまたは値を比較する場合)で、構造(値)に含まれるデータに割り込むためにさらに多くの作業が必要になります。
エヴァンプレイス

2
@ Wolfpack'08「メモリのアドレスは常にintになるようです」-次に、それらはすべてメモリ内の単なるビットであるため、タイプには何もないように見えます。「実際には、ポインタのタイプは、ポインタが指す変数のタイプです」-いいえ、ポインタのタイプは、ポインタが指す変数のタイプのポインタです-これは自然で明白なはずです。
ジムバルター2013

2
変数(および関数)は単なるメモリブロックであり、ポインタはメモリアドレスを格納する変数であるという事実を把握するのが難しいのではないかといつも思っていました。これは多分実用的すぎる思考モデルでは、抽象的な概念のすべてのファンに感銘を与えるとは限りませんが、ポインターがどのように機能するかを理解することは完全に役立ちます。
Christian Rau 2013年

8
一言で言えば、コンピュータのメモリ一般、特にCの「メモリモデル」がどのように機能するかを正しく理解していない、またはまったく理解していないため、学生はおそらく理解していません。この本は、地上からのプログラミングは、これらのトピックの非常に良い教訓を与えます。
アバフェイ2013年

回答:


745

ポインターは、多くの人にとって最初は混乱する可能性がある概念です。特に、ポインター値をコピーして同じメモリーブロックを参照する場合は特にそうです。

一番の類似点は、ポインターを家の住所が記載された紙のようなものと見なし、メモリブロックが実際の家として参照することです。したがって、あらゆる種類の操作を簡単に説明できます。

以下にDelphiコードをいくつか追加し、必要に応じてコメントもいくつか追加しました。Delphiを選択した理由は、他の主要なプログラミング言語であるC#が、メモリリークなどを同様に示さないためです。

ポインタの高レベルの概念のみを学習したい場合は、以下の説明で「メモリレイアウト」というラベルの付いた部分を無視してください。これらは、操作後のメモリの例を示すことを目的としていますが、実際には低レベルです。ただし、バッファオーバーランが実際にどのように機能するかを正確に説明するために、これらの図を追加することが重要でした。

免責事項:すべての意図と目的のために、この説明とメモリレイアウトの例は大幅に簡略化されています。低レベルでメモリを処理する必要がある場合、知っておく必要のあるオーバーヘッドと詳細が多くなります。ただし、メモリとポインタを説明する目的では、十分に正確です。


以下で使用されるTHouseクラスが次のようになっていると仮定します。

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

houseオブジェクトを初期化すると、コンストラクターに指定された名前がプライベートフィールドFNameにコピーされます。固定サイズの配列として定義されている理由があります。

メモリでは、家の割り当てに関連するオーバーヘッドがいくつかあります。これを以下のように説明します。

--- [ttttNNNNNNNNNN] ---
     ^ ^
     | |
     | +-FName配列
     |
     +-オーバーヘッド

「tttt」領域はオーバーヘッドです。通常、8バイトや12バイトなど、さまざまなタイプのランタイムや言語では、これよりも多くなります。この領域に格納されている値がメモリアロケータまたはコアシステムルーチン以外で変更されないようにする必要があります。変更しないと、プログラムがクラッシュするおそれがあります。


メモリを割り当てる

起業家に家を建ててもらい、家の住所を教えてください。現実の世界とは対照的に、メモリ割り当てはどこに割り当てるかを伝えることはできませんが、十分なスペースがある適切なスポットを見つけて、割り当てられたメモリにアドレスを報告します。

言い換えれば、起業家はスポットを選択します。

THouse.Create('My house');

メモリレイアウト:

--- [ttttNNNNNNNNNN] ---
    1234:私の家

変数をアドレスで保持する

新しい家の住所を紙に書き留めます。この紙はあなたの家への参照として役立ちます。この紙切れがなければ、あなたは迷子になっていて、家に入っていなければ家を見つけることができません。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

メモリレイアウト:

    h
    v
--- [ttttNNNNNNNNNN] ---
    1234:私の家

ポインタ値をコピー

新しい紙に住所を書いてください。これで、2つの別々の家ではなく、同じ家に行くための2枚の紙があります。1つの紙の住所をたどり、その家の家具を並べ替えようとすると、実際には1つの家だけであることが明確にわかる場合を除いて、他の家も同じように変更されているように見えます。

これは通常、人に説明する上で最も問題がある概念です。2つのポインタは、2つのオブジェクトまたはメモリブロックを意味しません。

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
--- [ttttNNNNNNNNNN] ---
    1234:私の家
    ^
    h2

メモリを解放する

家を破壊する。その後、必要に応じて、紙を新しい住所に再利用するか、クリアして、もう存在しない家の住所を忘れることができます。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

ここでは、まず家を建て、その住所を取得します。それから私は家に何かをし(それを使って...コード、読者の練習として残した)、それから私はそれを解放します。最後に、変数からアドレスをクリアします。

メモリレイアウト:

    h <-+
    v +-解放前
--- [ttttNNNNNNNNNN] --- |
    1234私の家<-+

    h(今はどこも指していない)<-+
                                +-解放後
---------------------- | (メモ、まだメモリ
    xx34私の家<-+いくつかのデータが含まれています)

ぶら下がりポインタ

あなたは起業家に家を破壊するように言いましたが、紙から住所を消すのを忘れています。後で一枚の紙を見ると、家がもうそこにないことを忘れて訪問しに行きましたが、失敗しました(以下の無効な参照に関する部分も参照)。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

h呼び出し後の使用.Free うまくいくかもしれませんが、それは単なる幸運です。ほとんどの場合、顧客の場所で、重要な操作の途中で失敗します。

    h <-+
    v +-解放前
--- [ttttNNNNNNNNNN] --- |
    1234私の家<-+

    h <-+
    v +-解放後
---------------------- |
    xx34私の家<-+

ご覧のとおり、hはメモリ内のデータの残りを指していますが、完全ではない可能性があるため、以前のように使用すると失敗する可能性があります。


メモリーリーク

あなたは紙切れを失い、家を見つけることができません。家はまだどこかに立っており、後で新しい家を建てたいときに、その場所を再利用することはできません。

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

ここでは、h変数の内容を新しい家の住所で上書きしましたが、古い家はまだ残っています...どこかにあります。このコードの後、その家に到達する方法はなく、そのまま残されます。つまり、割り当てられたメモリは、アプリケーションが閉じるまで割り当てられたままであり、その時点でオペレーティングシステムはアプリケーションを破棄します。

最初の割り当て後のメモリレイアウト:

    h
    v
--- [ttttNNNNNNNNNN] ---
    1234:私の家

2番目の割り当て後のメモリレイアウト:

                       h
                       v
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234私の家5678私の家

このメソッドを取得するより一般的な方法は、上記のように上書きする代わりに、何かを解放するのを忘れることです。Delphiの用語では、これは次の方法で発生します。

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

このメソッドが実行された後、家の住所が存在する変数はありませんが、家はまだそこにあります。

メモリレイアウト:

    h <-+
    v +-ポインターを失う前
--- [ttttNNNNNNNNNN] --- |
    1234私の家<-+

    h(今はどこも指していない)<-+
                                +-ポインタを失った後
--- [ttttNNNNNNNNNN] --- |
    1234私の家<-+

ご覧のように、古いデータはメモリにそのまま残り、メモリアロケータによって再利用されません。アロケータは、メモリのどの領域が使用されたかを追跡し、解放しない限りそれらを再利用しません。


メモリを解放するが(現在は無効)参照を保持する

家を解体し、紙の1つを消去しますが、古い住所が記載された別の紙もあります。住所に移動すると、家は見つかりませんが、廃墟のようなものが見つかるかもしれません。ひとつの。

おそらくあなたは家を見つけるかもしれませんが、それはあなたが最初にアドレスを与えられた家ではないので、それがあなたのものであるかのようにそれを使用する試みは恐ろしく失敗するかもしれません。

ときどき、隣接する住所に3つの住所(メインストリート1-3)を占めるかなり大きな家が設定されていて、家の真ん中にあなたの住所が表示されることがあります。大きな3アドレスの家のその部分を1つの小さな家として扱う試みも、恐ろしく失敗する可能性があります。

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

ここでは家はで参照を通じて、取り壊されたh1、としながらh1も外された、h2古い、古く、アドレスを持っています。立っていない家へのアクセスは、機能する場合と機能しない場合があります。

これは、上記のぶら下がりポインタのバリエーションです。そのメモリレイアウトをご覧ください。


バッファオーバーラン

あなたが家に入れることができるよりも多くのものを家に移し、隣人の家や庭にこぼします。後でその隣の家の所有者が家に帰ると、彼は自分が所有していると思うあらゆる種類の物を見つけます。

これが、固定サイズの配列を選択した理由です。ステージを設定するために、割り当てた2番目の家が、何らかの理由で、メモリ内の最初の家の前に配置されると仮定します。つまり、2番目の家の住所は最初の家の住所よりも低くなります。また、それらは隣同士に割り当てられます。

したがって、このコード:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

最初の割り当て後のメモリレイアウト:

                        h1
                        v
----------------------- [ttttNNNNNNNNNN]
                        5678私の家

2番目の割り当て後のメモリレイアウト:

    h2 h1
    vv
--- [ttttNNNNNNNNNN] ---- [ttttNNNNNNNNNN]
    1234:他の家のどこかで
                        ^ --- +-^
                            |
                            +-上書き

クラッシュを最も頻繁に引き起こすのは、実際にランダムに変更すべきではない、保存したデータの重要な部分を上書きする場合です。たとえば、プログラムのクラッシュに関してh1-houseの名前の一部が変更されても問題ではないかもしれませんが、壊れたオブジェクトを使用しようとすると、オブジェクトのオーバーヘッドを上書きするとクラッシュする可能性が高くなります。オブジェクト内の他のオブジェクトに保存されているリンクを上書きする。


リンクされたリスト

一枚の紙の住所をたどると、家に着きます。その家には、新しい住所が書かれた別の紙があり、チェーンの次の家などに続きます。

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

ここでは、家からキャビンへのリンクを作成します。家にNextHouse参照がなくなるまで、つまり最後の家になるまで、チェーンをたどることができます。すべての家を訪問するには、次のコードを使用できます。

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

メモリレイアウト(オブジェクトにリンクとしてNextHouseを追加、下の図の4つのLLLLで注記):

    h1 h2
    vv
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234ホーム+ 5678キャビン+
                   | ^ |
                   + -------- + *(リンクなし)

基本的に、メモリアドレスとは何ですか?

メモリアドレスは、基本的には単なる数値です。メモリをバイトの大きな配列と考える場合、最初のバイトにはアドレス0があり、次のバイトにはアドレス1が続きます。これは単純化されていますが、十分です。

したがって、このメモリレイアウト:

    h1 h2
    vv
--- [ttttNNNNNNNNNN] --- [ttttNNNNNNNNNN]
    1234私の家5678私の家

次の2つのアドレスがある場合があります(左端が-アドレス0です)。

  • h1 = 4
  • h2 = 23

つまり、上記のリンクリストは実際には次のようになります。

    h1(= 4)h2(= 28)
    vv
--- [ttttNNNNNNNNNNLLLL] ---- [ttttNNNNNNNNNNLLLL]
    1234ホーム0028 5678キャビン0000
                   | ^ |
                   + -------- + *(リンクなし)

通常、「どこも指し示さない」アドレスをゼロアドレスとして格納します。


基本的に、ポインタとは何ですか?

ポインタは、メモリアドレスを保持する単なる変数です。通常、プログラミング言語に番号を要求することができますが、ほとんどのプログラミング言語とランタイムは、番号自体が実際には意味を持たないという理由だけで、その下に番号があるという事実を隠そうとします。ポインタはブラックボックスと考えるのが最適です。それが機能する限り、実際にどのように実装されているかは、本当に知りませんし、気にしません。


59
バッファオーバーランは陽気です。「隣人が家に帰ってきて、あなたのがらくたの上を滑って頭蓋骨を割って開き、あなたを忘却に訴えます。」
gtd 2009

12
確かに、これはコンセプトの素晴らしい説明です。この概念は、ポインターについて混乱させるものではないので、このエッセイ全体は少し無駄でした。
ブルトン

10
しかし、単に尋ねているように、何を、あなたは、ポインタについての混乱を見つけますか?
ラッセV.カールセン

11
あなたが答えを書いて以来、私はこの投稿を何度か再訪しました。コードの明確化はすばらしいものです。考えを追加/調整するためにコードを再検討していただきありがとうございます。ブラボーラッセ!
David McGraw、

3
テキストの長さが1ページでも、メモリ管理、参照、ポインタなどのすべてのニュアンスを要約することはできません。465人がこれに賛成票を投じたので、十分に役立つと思います。情報の開始ページ。さらに学ぶことはありますか?はい、いつですか?
Lasse V. Karlsen 2013

153

私の最初のComp Sciクラスでは、次の演習を行いました。確かに、これはおよそ200人の学生がいる講堂でした...

教授はボードにこう書いている: int john;

ジョンは立ち上がる

教授は書く: int *sally = &john;

サリーは立ち上がる、ジョンをポイントする

教授: int *bill = sally;

ビルは立ち上がってジョンを指さす

教授: int sam;

サムは立ち上がる

教授: bill = &sam;

ビルは今、サムを指しています。

あなたはアイデアを理解していると思います。ポインタ割り当ての基本を説明するまで、これに約1時間費やしたと思います。


5
私はそれを間違ったとは思いません。私の意図は、指す変数の値をジョンからサムに変更することでした。両方のポインタの値を変更しているように見えるため、人で表現するのは少し難しいです。
トライク

24
しかし、それが混乱している理由は、ジョンが彼の席から立ち上がって、サムが座ったようではないということです。それは、サムがやって来てジョンに手を突き刺し、サムのプログラミングをジョンの体にクローンしたようなものだ。
ブルトン

59
サムのようにジョンが席に着くと、ジョンは部屋の周りを浮遊し、重要な何かにぶつかってセグフォルトを引き起こします。
just_wes 2010年

2
私はこの例を不必要に複雑にしています。教授は私にライトを指すように言い、「あなたの手はライトオブジェクトへのポインターです」と言いました。
Celeritas 2013年

このタイプの例の問題は、XとXへのポインターが同じでないことです。そして、これは人々と一緒に描かれていません。
Isaac Nequittepas 2013

124

ポインタの説明に役立つと思われる類推は、ハイパーリンクです。ほとんどの人は、Webページ上のリンクがインターネット上の別のページを「指している」ことを理解できます。そのハイパーリンクをコピーして貼り付けることができれば、どちらも同じ元のWebページを指します。その元のページに移動して編集した場合、これらのリンク(ポインタ)のいずれかをたどると、新しい更新されたページが表示されます。


15
私は本当にこれが好きです。ハイパーリンクを2回書き込んでも、2つのWebサイトが表示されないことは簡単です(int *a = bが2つのコピーを作成しないように*b)。
10

4
これは実際には非常に直感的であり、誰もが関係することができるはずです。このアナロジーがばらばらになるシナリオはたくさんありますが。簡単な紹介にも最適です。+1
ブライアンウィギントン

2回開かれているページへのリンクは通常、そのWebページのほぼ完全に独立した2つのインスタンスを作成します。ハイパーリンクは、おそらくコンストラクターによく似ているかもしれませんが、ポインターには似ていないと思います。
Utkan Gezer 2014

@ThoAppelsinたとえば、静的なHTML Webページにアクセスしている場合は、サーバー上の1つのファイルにアクセスしているとは限りません。
dramzy

5
難しく考えすぎだよ。ハイパーリンクは、サーバー上のファイルを指します。これが類推の範囲です。
ドラマー2015年

48

ポインタが非常に多くの人々を混乱させるように見える理由は、彼らがほとんどコンピュータアーキテクチャの背景をほとんどまたはまったく持っていないためです。多くの人はコンピュータ(マシン)が実際にどのように実装されているかを理解していないようです-C / C ++での作業は異質なようです。

ドリルは、単純なバイトコードベースの仮想マシン(選択した任意の言語で、Pythonはこれに最適です)を、ポインター操作(ロード、ストア、直接/間接アドレス指定)に焦点を合わせて実装するように依頼することです。次に、その命令セット用の簡単なプログラムを作成するように依頼します。

単純な追加より少しだけ多くのことを必要とするものはすべてポインタを含み、彼らはそれを確実に得るでしょう。


2
面白い。しかし、これについてどうやって始めるかはわかりません。共有するリソースはありますか?
Karolis

1
同意する。たとえば、Cの前にアセンブリでプログラミングする方法を学び、レジスターがどのように機能するかを知り、ポインターを学ぶのは簡単でした。実際、学習はそれほど多くなく、すべて自然なことでした。
ミラノバブシュコフ2009年

基本的なCPUを取り上げ、芝刈り機や食器洗い機を実行するものを言って、それを実装します。または、非常に基本的なARMまたはMIPSのサブセット。どちらにも非常にシンプルなISAがあります。
Daniel Goldberg、

1
この教育的アプローチがドナルド・クヌース自身によって提唱/実践されていることを指摘する価値があるかもしれません。KnuthのArt of Computer Programmingは、単純な仮想アーキテクチャについて説明し、そのアーキテクチャの仮想アセンブリ言語で問題を実践するためのソリューションを実装するように学生に依頼しています。それが実用的になると、Knuthの本を読んでいる学生の中には、実際に彼のアーキテクチャをVMとして実装(または既存の実装を使用)し、実際にソリューションを実行する人もいます。IMOこれは時間があれば、学ぶのに最適な方法です。
WeirdlyCheezy

4
@Lukeポインターを簡単に把握できない人(または、より正確に言うと、一般的に間接参照)を理解するのはそれほど簡単ではないと思います。基本的に、Cのポインターを理解していない人がアセンブリーの学習を開始し、コンピューターの基本的なアーキテクチャーを理解し、ポインターを理解してCに戻ることができると想定しています。これは多くの人に当てはまるかもしれませんが、一部の研究によると、一部の人々は本質的には本質的に間接を理解できないようです(それでも信じられないことはまだありますが、おそらく「学生」とは幸運でした")。
Luaan

27

ポインターが、C / C ++言語の多くの新しい、さらには古い大学レベルの学生にとって、なぜ混乱の主な要因なのでしょうか。

値のプレースホルダーの概念-変数-学校で教えられているもの-代数にマップします。コンピュータ内でメモリが物理的にどのように配置されているかを理解せずに描くことができる既存のパラレルはありません。低レベルのもの(C / C ++ /バイト通信レベル)を扱うまで、この種のことについて誰も考えません。

ポインターが変数、関数、およびレベルを超えてどのように機能するかを理解するのに役立つツールまたは思考プロセスはありますか?

ボックスに対処します。BASICをマイクロコンピュータにプログラムすることを学んでいたとき、ゲームが書かれたこれらのかわいい本があり、特定のアドレスに値を突き刺さなければならないこともあったことを覚えています。彼らは、0、1、2 ...とラベルが付けられた一連のボックスの写真を持っていて、これらのボックスには1つの小さなもの(バイト)しか収まらないと説明され、それらの多くがありました-一部のコンピュータは65535もありました。彼らは互いに隣り合っており、彼ら全員が住所を持っていました。

誰かが「ああ、わかった」のレベルに到達するために、全体的なコンセプトに夢中にならないようにするために実行できるいくつかの優れた実践方法は何ですか?基本的に、シナリオのようにドリルします。

ドリル?構造体を作る:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

Cを除いて、上記と同じ例:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

出力:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

多分それは例を通していくつかの基本を説明しますか?


+1は「メモリが物理的にどのように配置されているかを理解せずに」です。私はアセンブリ言語のバックグラウンドからCに来ましたが、ポインターの概念は非常に自然で簡単でした。そして私はそれを理解するために、より高いレベルの言語の背景しか持っていない人々を見てきました。さらに悪いことに、構文は混乱します(関数ポインター!)。そのため、概念と構文を同時に学習することは、トラブルのレシピです。
ブレンダン

4
私はこれが古い投稿であることを知っていますが、提供されたコードの出力が投稿に追加されれば素晴らしいでしょう。
Josh、

ええ、それは代数に似ています(ただし、代数には「変数」を不変にすることで理解しやすい点があります)。しかし、私が知っている人々の約半分は、実際には代数を理解していません。それはそれらのために計算しないだけです。彼らは結果にたどり着くためのそれらすべての「方程式」と処方箋を知っていますが、それらをいくぶんランダムにそして不器用に適用します。そして、彼らは彼ら自身の目的のためにそれらを拡張することはできません-それは彼らのためのいくつかの変更できない、構成できないブラックボックスです。代数を理解し、それを効果的に使用できる場合、プログラマーの間でさえ、すでにパックをはるかに上回っています。
Luaan

24

私が最初にポインタを理解するのに苦労した理由は、多くの説明には参照渡しについての多くのがらくたが含まれているためです。これは問題を混乱させるだけです。ポインターパラメーターを使用する場合、値で渡します。しかし、値はたまたま、たとえば、intではなくアドレスです。

他の誰かがすでにこのチュートリアルにリンクしていますが、ポインタを理解し始めた瞬間を強調できます。

Cのポインターと配列に関するチュートリアル:第3章-ポインターと文字列

int puts(const char *s);

今のところ、const.渡されるパラメータputs()はポインタ、つまりポインタの値です(Cのすべてのパラメータは値で渡されるため)を無視し、ポインタの値はポインタが指すアドレス、または単に、 アドレス。したがって、これputs(strA);まで見てきたように書き込むとき、strA [0]のアドレスを渡します。

私がこれらの言葉を読んだ瞬間、雲が晴れ、太陽光線がポインターの理解に包まれました。

VB .NETまたはC#の開発者(私と同じ)で安全でないコードを使用していない場合でも、ポインターのしくみを理解することは価値があります。そうでない場合、オブジェクト参照のしくみを理解できません。次に、オブジェクト参照をメソッドに渡すとオブジェクトがコピーされるという一般的だが誤った概念があります。


ポインタを持っていることの意味は何なのかと私は思います。私が遭遇するほとんどのコードブロックでは、ポインターはその宣言でのみ表示されます。
Wolfpack'08

@ Wolfpack'08 ...なに?? どのコードを見ていますか?
カイルストランド

@KyleStrand見てみましょう。
Wolfpack'08

19

Ted Jensenの「Cのポインターと配列のチュートリアル」は、ポインターについて学ぶための優れたリソースでした。これは10レッスンに分かれており、最初はポインターの説明(およびポインターの目的)から、関数ポインターで終わります。http://home.netcom.com/~tjensen/ptr/cpoint.htm

次に、Beejのネットワークプログラミングガイドでは、UnixソケットAPIについて説明します。ここから、本当に楽しいことができるようになります。http://beej.us/guide/bgnet/


1
Ted Jensenのチュートリアルの2番目です。それは詳細レベルへのポインタを分解します。それは過度に詳細ではなく、私が読んだ本はありません。とても役に立ちました!:)
デイブギャラガー

12

ポインターの複雑さは、私たちが簡単に教えることができる範囲を超えています。生徒にお互いを指差させ、家の住所が書かれた紙を使用することは、どちらも優れた学習ツールです。彼らは基本的な概念を紹介するのに素晴らしい仕事をします。実際、基本的な概念を学ぶことは、ポインタをうまく使用するために不可欠です。ただし、量産コードでは、これらの単純なデモンストレーションでカプセル化できるよりもはるかに複雑なシナリオに入るのが一般的です。

私は、他の構造を指す他の構造を指す構造を持つシステムに関与してきました。それらの構造の一部には、(追加の構造へのポインターではなく)埋め込み構造も含まれていました。これは、ポインタが本当に混乱する場所です。複数レベルの間接参照があり、次のようなコードで終わる場合:

widget->wazzle.fizzle = fazzle.foozle->wazzle;

それは本当にすぐに混乱する可能性があります(より多くの行、そして潜在的にはより多くのレベルを想像してください)。ポインターの配列、およびノー​​ド間ポインター(ツリー、リンクリスト)をスローしても、さらに悪化します。このようなシステムに取り組み始めると、基本をよく理解している開発者でさえ、本当に優秀な開発者が迷子になるのを見てきました。

ポインターの複雑な構造は必ずしもコーディングが不十分であることを示しているわけではありません(可能ですが)。合成は優れたオブジェクト指向プログラミングの不可欠な要素であり、生のポインタを使用する言語では、必然的に多層の間接化につながります。さらに、システムは多くの場合、スタイルや手法が互いに一致しない構造を持つサードパーティのライブラリを使用する必要があります。そのような状況では、複雑さが自然に発生します(確かに、できる限りそれと戦う必要があります)。

大学がポインタを学ぶのを助けるために大学ができる最善のことは、ポインタの使用を必要とするプロジェクトと組み合わせて、良いデモンストレーションを使用することだと思います。困難なプロジェクトの1つは、1,000のデモよりもポインタの理解に多くのことを行います。デモンストレーションは浅い理解を得ることができますが、ポインタを深く理解するには、実際に使用する必要があります。


10

このリストに類推を加えて、コンピュータサイエンスの講師として(当時の)ポインタを説明するときに非常に役立つと思いました。まず、みましょう:


ステージを設定します。

3つのスペースがある駐車場を考えてみましょう。これらのスペースには番号が付けられています。

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

ある意味で、これはメモリの場所のようなもので、シーケンシャルで連続しています。配列のようなものです。現時点では車は存在しないため、空の配列(parking_lot[3] = {0})のようになります。


データを追加する

駐車場は長い間空にとどまることは決してありません...もしそうならそれは無意味で誰も何も建てません。それで、日が移動するにつれて、多くのものが3台の車、青い車、赤い車、そして緑の車でいっぱいになるとしましょう:

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

これを考えるための一つの方法は、我々のクルマは、データのいくつかの並べ替えです(と言うことであるように、これらの車は、すべて同じタイプ(車)ですint)が、それらは異なる値(持っているblueredgreen、色かもしれませんenum


ポインタを入力してください

さて、この駐車場にあなたを連れて行き、青い車を見つけるように頼んだら、1本の指を伸ばして、スポット1の青い車を指し示します。これは、ポインタを取得してメモリアドレスに割り当てるようなものです(int *finger = parking_lot

あなたの指(ポインタ)は私の質問に対する答えではありません。探している相手あなたの指は、私には何も伝えていないが、私はあなたがしている指がどこにあるかに見える場合を指す(ポインタを逆参照)、私は私が探していた車(データを)見つけることができます。


ポインターの再割り当て

代わりに赤い車を探すように頼むことができ、指を新しい車にリダイレクトすることができます。これで、ポインター(以前と同じ)に、同じタイプ(車)の新しいデータ(赤い車が見つかる駐車スポット)が表示されます。

ポインタが物理的に変更されていない、それはまだだ、あなたの指、それは私を見せていただけのデータが変更されました。(「駐車場」住所)


二重ポインター(またはポインターへのポインター)

これは複数のポインタでも機能します。赤い車を指しているポインターがどこにあるか尋ねることができます。もう一方の手を使用して、指で人差し指を指すことができます。(これはのようですint **finger_two = &finger

青い車がどこにあるかを知りたい場合は、1本目の指の方向を2本目の指の車(データ)までたどることができます。


ぶら下がりポインタ

今、あなたは彫像のようにとても感じていて、赤い車をいつまでも指さしたままにしたいとしましょう。その赤い車が追い払われたらどうなりますか?

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

あなたのポインターはまだ赤い車があった場所を指していますが、もはやそうではありません。新しい車がそこにやって来たとしましょう...オレンジ色の車。ここでもう一度「赤い車はどこですか」と尋ねると、あなたはまだそこを指していますが、今は間違っています。赤い車ではなく、オレンジです。


ポインター演算

はい、まだ2番目の駐車場を指しています(今はオレンジ色の車で占められています)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

さて、新しい質問があります... 次の駐車場の車の色を知りたいです。スポット2を指していることがわかります。1を追加するだけで、次のスポットを指していることになります。(finger+1)、データが何であるかを知りたいので、その場所(指だけではなく*(finger+1))を確認して、ポインター()を参照して、そこに緑色の車が存在することを確認する必要があります(その場所のデータ) )


「ダブルポインター」という言葉は使用しないでください。ポインターは任意のものを指すことができるので、明らかに他のポインターを指すポインターを持つことができます。それらは二重ポインターではありません。
gnasher729 2014年

これは、あなたの類推を続けると、「指」自体がそれぞれ「駐車場を占有する」という点を逃していると思います。類推の高レベルの抽象化でポインターを理解するのが難しいかどうかはわかりません。ポインターは、メモリロケーションを占有する変更可能なものであり、これがどのように役立つかを理解しているため、人々を回避しているようです。
Emmet 2014

1
@Emmet-WRTポインターに入る可能性のある人が多いことに異論はありませんが、質問を読みます:"without getting them bogged down in the overall concept"高レベルの理解として。そしてあなたの要点:"I'm not sure that people have any difficulty understanding pointers at the high level of abstraction"-あなたはこのレベルにさえポインタを理解していない多くの人々を驚かせることでしょう
マイク

車の指の類推を人に拡張するメリットはありますか(1本または複数の指と、それぞれの指を任意の方向に向けることができる遺伝的異常!)別の車(または「初期化されていないポインタ」として区画の横の荒れ地を指さした、または「固定サイズの[5]ポインタの配列」としてスペースの行を指して手全体が広がった、または手のひらに「ヌルポインタ」が丸まったそれは車が決してないことが知られている場所を指します)...)8
SlySven

あなたの説明は理解でき、初心者には良い説明でした。
Yatendra Rathore 2017

9

概念としてのポインターは特に難しいとは思いません。ほとんどの学生のメンタルモデルはこのようなものにマッピングされており、いくつかのクイックボックススケッチが役立ちます。

少なくとも私が過去に経験し、他の人が対処しているのを見たときの難しさは、C / C ++でのポインターの管理が不必要に複雑になり得ることです。


9

一連の適切な図を使用したチュートリアルの例は、ポインタを理解するのに非常に役立ちます

ジョエル・スポルスキーは、インタビューの記事への彼のゲリラガイドでポインターを理解することについていくつかの良い点を述べています:

何らかの理由で、ほとんどの人はポインターを理解する脳の部分なしで生まれているようです。これは適性の問題であり、スキルの問題ではありません。一部の人が実行できない複雑な形の二重間接的思考が必要です。


8

ポインターの問題は概念ではありません。それは実行と関与する言語です。教師が難しいのはポインタの概念であり、専門用語ではない、または複雑な混乱したCおよびC ++が概念を作成していると教師が想定すると、さらに混乱が生じます。だから、この質問の受け入れられた答えのように、コンセプトの説明に多大な労力が費やされ、私はすでにすべてを理解しているので、それは私のような誰かにかなり無駄にされています。問題の間違った部分を説明しているだけです。

私がどこから来たのかを説明するために、私はポインターを完全によく理解している人であり、アセンブラー言語で十分にそれらを使用できます。アセンブラー言語では、これらはポインターと呼ばれていません。それらはアドレスと呼ばれます。Cでのプログラミングとポインターの使用については、多くの間違いを犯し、本当に混乱しています。私はまだこれを整理していません。例を挙げましょう。

APIが言うとき:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

何が欲しいの?

それはしたいかもしれません:

バッファへのアドレスを表す数値

(それを与えるために、私は言うかdoIt(mybuffer)、それともdoIt(*myBuffer)?)

アドレスからバッファへのアドレスを表す数値

(それはdoIt(&mybuffer)or doIt(mybuffer)またはdoIt(*mybuffer)?)

バッファへのアドレスへのアドレスへのアドレスを表す数

(多分それdoIt(&mybuffer)は。またはそれdoIt(&&mybuffer)ですか?またはdoIt(&&&mybuffer)

など、そして「xはyへのアドレスを保持します」および「この関数にはyへのアドレスが必要です。さらに、答えは、一体何が "mybuffer"から始まるのか、何をするつもりなのかに依存します。この言語は、実際に発生する入れ子のレベルをサポートしていません。新しいバッファを作成する関数に「ポインタ」を渡さなければならないときのように、バッファの新しい場所を指すようにポインタを変更します。本当にポインタが必要ですか、それともポインタへのポインタが必要ですか?それで、ポインタの内容を変更するためにどこへ行くかがわかります。ほとんどの場合、私は「」の意味を推測する必要があります

「ポインタ」が多すぎます。ポインタは値へのアドレスですか?または、値へのアドレスを保持する変数です。関数がポインターを必要とする場合、ポインター変数が保持するアドレスが必要ですか、それともポインター変数へのアドレスが必要ですか?よくわかりません。


私はそれがこのように説明されるのを見ました:のようなポインター宣言を見ればdouble *(*(*fn)(int))(char)、評価の結果はに*(*(*fn)(42))('x')なりますdouble。評価の層を取り除いて、中間タイプがどうあるべきかを理解することができます。
Bernd Jendrissek 2013

@BerndJendrissekフォローしているかわかりません。そのとき評価した結果は何 (*(*fn)(42))('x') ですか?
ブルトン語

x評価*xした場合、double が得られるもの(それをと呼ぶことにします)を取得します。
Bernd Jendrissek 2013

@BerndJendrissekこれはポインタについて何かを説明するはずですか?わかりません。あなたのポイントは何ですか?私はレイヤーを取り除いて、中間タイプに関する新しい情報を得ていません。特定の関数が何を受け入れるかについては何を説明していますか?それは何と関係がありますか?
ブルトン語

たぶん、この説明でメッセージ(と、それは私のものではない、私は私がどこで最初のこぎりそれを見つけることがしたい)が少ないものの観点で考えているfn であるあなたができるかという点で、より行うfn
ベルントJendrissek

8

ポインタを理解する上での主な障壁は悪い教師だと思います。

ほとんどすべての人がポインタについてうそをつきます。それらはメモリアドレスにすぎないこと、または任意の場所をポイントできるようにすることです。

そしてもちろん、それらは理解するのが難しく、危険で半魔法です。

どれも真実ではありません。ポインタは、C ++言語がそれらについて述べなければならないことに固執し、「通常は」実際に機能することが判明しているが、言語によって保証されていない属性を付けない限り、実際にはかなり単純な概念です。 、およびそのため、ポインタの実際の概念の一部ではありません。

数か月前にこのブログ投稿これについての説明を書き込もうとしました-うまくいけば誰かの助けになるでしょう。

(注意:だれかが私に夢中になる前に、はい、C ++標準はポインタメモリアドレスを表すと言っています。しかし、「ポインタはメモリアドレスであり、メモリアドレスにすぎず、メモリと交換可能に使用または考えられるかもしれません。アドレス」。区別は重要です)


結局のところ、Cの「値」がゼロであっても、nullポインタはメモリ内のゼロアドレスを指しません。これは完全に別の概念であり、間違って対処すると、予期しないものに対処(および逆参照)する可能性があります。場合によっては、それはメモリ内のゼロアドレスでさえある可能性があります(特に現在、アドレススペースは通常フラットです)が、最適化コンパイラによって未定義の動作として省略されるか、関連付けられているメモリの他の一部にアクセスする場合があります与えられたポインタ型については「ゼロ」で。陽気さが続く。
Luaan

必ずしも。ポインターが意味をなすように(そして他のプログラムもデバッグするために)、頭の中でコンピューターをモデル化できる必要があります。誰でもできるわけではありません。
–ThorbjørnRavn Andersen 2018

5

ポインタを覚えるのが難しいのは、ポインタがなくなるまでは、「このメモリ位置には、int、double、characterなどを表すビットのセットがある」という考えに慣れるまでだと思います。

最初にポインタが表示されたとき、そのメモリ位置にあるものを実際には取得していません。「どういう意味ですか、それはアドレスを保持していますか?」

「あなたはそれらを手に入れるか、または手に入れない」という考えには同意しません。

大きな構造を関数に渡さないなど、実際の用途を見つけ始めると、それらは理解しやすくなります。


5

理解するのが難しいのは、難しい概念ではなく、構文に一貫性がないためです。

   int *mypointer;

最初に、変数作成の左端の部分が変数のタイプを定義することを学びます。ポインター宣言は、CおよびC ++ではこのように機能しません。代わりに、変数は型が左を指していると言います。この場合、*mypointer intを指しています。

ポインターをC#で(安全でない状態で)使用するまでは、ポインターを完全には把握していませんでした。ポインターはまったく同じように機能しますが、論理的で一貫性のある構文です。ポインタはそれ自体が型です。ここで、mypointer intへのポインターです。

  int* mypointer;

関数ポインタから始めないでください...


2
実際、どちらのフラグメントも有効なCです。最初のフラグメントがより一般的であるのは、長年のCスタイルの問題です。2番目は、たとえばC ++ではかなり一般的です。
RBerteig 2009

1
2番目のフラグメントは、より複雑な宣言では実際にはうまく機能しません。そして、ポインタ宣言の右側の部分が、左側の原子型指定子である型を持つものを取得するためにポインタに対して何をしなければならないかを示していると理解すれば、構文は「一貫性がありません」。
Bernd Jendrissek 2013

2
int *p;簡単な意味があります:*pは整数です。int *p, **pp意味:*pおよび**pp整数です。
Miles Rout 2014年

@MilesRout:しかし、それがまさに問題です。*pそして、**ppしているではない、あなたが初期化されませんので、整数pまたはppまたは*pp何にもポイントに。特に一部のエッジケースや複雑なケースではそうする必要があるため、一部の人々がこの文法にこだわるのを好む理由を理解しています(ただし、私が知っているすべてのケースでそれを簡単に回避できます)...しかし、私はそれらのケースが右揃えを教えることが初心者に誤解を招くという事実よりも重要だとは思いません。醜いことは言うまでもありません!:)
オービットのライトネスレース

@LightnessRacesinOrbit右揃えを教えることは誤解を招くほどではありません。それはそれを教える唯一の正しい方法です。それを教えることは誤解を招くものです。
Miles Rout

5

C ++を知っているだけでポインタを操作できました。場合によっては何をすべきか、試行錯誤から何をすべきでないかを私はある程度知っていました。しかし、私に完全に理解を与えたのはアセンブリ言語です。作成したアセンブリ言語プログラムで深刻な命令レベルのデバッグを行うと、多くのことを理解できるはずです。


4

私は家の住所の類推が好きですが、私はいつも住所が郵便受け自体にあることを考えていました。このようにして、ポインターの逆参照(メールボックスを開く)の概念を視覚化できます。

たとえば、リンクされたリストをたどる:1)紙のアドレスから始めます2)紙のアドレスに移動します3)メールボックスを開いて、次のアドレスの新しい紙を見つけます

線形リンクリストでは、最後のメールボックスには何もありません(リストの最後)。循環リンクリストでは、最後のメールボックスには最初のメールボックスのアドレスが含まれます。

ステップ3は逆参照が発生する場所であり、アドレスが無効な場合にクラッシュまたは失敗することに注意してください。無効なアドレスのメールボックスに近づく可能性があると仮定して、そこにブラックホールまたは世界を裏返す何かがあると想像してください。


メールボックス番号のアナロジーの厄介な複雑さは、デニス・リッチーによって発明された言語がバイトのアドレスとそれらのバイトに格納された値の観点から動作を定義する一方で、C標準で定義された言語は動作を使用するように「最適化」実装を勧めているということですより複雑ですが、モデルのさまざまな側面をあいまいで矛盾し、不完全な方法で定義します。
スーパーキャット2018

3

人々がそれに悩む主な理由は、それが一般的に興味深く魅力的な方法で教えられていないからだと思います。講師が群集から10人のボランティアを獲得し、それぞれに1メートルの定規を与え、特定の構成で立ってもらい、定規を使用してお互いを指すようにしてほしいです。次に、人々を動かして(そして彼らが定規を指す場所に)ポインター算術を示します。これは、メカニズムに夢中になることなく、コンセプトを示すシンプルで効果的な(そして何よりも覚えやすい)方法です。

CとC ++に到達すると、一部の人にとっては難しくなるようです。彼らが最終的に適切に理解していない理論を実行に移しているためか、ポインターの操作がこれらの言語では本質的に難しいためか、私にはわかりません。私自身のトランジションはよく覚えていませんが、Pascalでポインターを知っていて、Cに移動して完全に失われました。


2

ポインタ自体が混乱しているとは思わない。ほとんどの人はその概念を理解できます。これで、ポインタの数や、間接参照のレベルの数に慣れることができます。人を追い越すのにそれほど多くの時間はかかりません。プログラムのバグによって誤って変更される可能性があるという事実は、コードに問題が発生したときにデバッグを非常に困難にする可能性もあります。


2

実際には構文の問題かもしれません。ポインターのC / C ++構文は一貫性がなく、必要以上に複雑に見えます。

皮肉なことに、実際にポインタを理解するのに役立つのは、c ++ 標準テンプレートライブラリのイテレータの概念に出会ったことです。皮肉なのは、反復子がポインタの一般化として考えられたとしか想定できないからです。

時々、あなたは木を無視することを学ぶまで森を見ることはできません。


問題は主にC宣言構文にあります。しかし場合は、ポインタの使用は必ず容易になるだろう(*p)されているだろう(p->)ので、私たちは持っていると思いますp->->x代わりに曖昧で*p->x
MSalters

@MSaltersああ、冗談を言ってるわよね?そこに矛盾はありません。a->b単にを意味し(*a).bます。
Miles Rout 2014年

@Miles:確かに、そのロジックによる* p->x手段* ((*a).b)に対し、*p -> x手段(*(*p)) -> x。前置演算子と後置演算子を混在させると、あいまいな解析が発生します。
MSalters 2014年

@MSaltersいいえ、空白は無関係です。それはそれ1+2 * 3が9であるべきだと言っているようなものです
Miles Rout

2

混乱は、「ポインター」の概念で混合された複数の抽象化レイヤーに起因します。プログラマーは、Java / Pythonの通常の参照に混乱することはありませんが、ポインターは、基礎となるメモリーアーキテクチャーの特性を公開するという点で異なります。

抽象化の層を明確に分離することは良い原則であり、ポインタはそれを行いません。


1
興味深いのは、Cポインターは実際には基礎となるメモリアーキテクチャの特性を公開しないということです。Java参照とCポインターの唯一の違いは、ポインターを含む複雑な型(たとえば、int ***またはchar *()(void *))を使用できることです。配列および構造体メンバーへのポインターの算術演算、存在void *、配列/ポインタの双対性。それ以外は、まったく同じように機能します。
jpalecek 2009年

いい視点ね。それは、ポインタ演算とバッファオーバーフローの可能性です。つまり、現在関連するメモリ領域から抜け出すことによって抽象化から抜け出すことです。
ジョシュアフォックス

@jpalecek:基盤となるアーキテクチャーの観点からそれらの動作を文書化する実装でポインターがどのように機能するかを理解するのは非常に簡単です。言うとfoo[i]は、特定の場所に行き、特定の距離だけ前進し、そこにあるものを見るということです。物事を複雑にするのは、コンパイラーの利益のために標準によって追加された、はるかに複雑な追加の抽象化レイヤーですが、プログラマーのニーズとコンパイラーのニーズに似合わない方法で物事をモデル化します。
スーパーキャット2018

2

私がそれを説明するのが好きだった方法は、配列とインデックスに関してでした-人々はポインタに慣れていないかもしれませんが、彼らは一般にインデックスが何であるかを知っています。

したがって、RAMが配列であると想像してください(RAMは10バイトしかありません)。

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

次に、変数へのポインタは、実際にはRAM内のその変数(の最初のバイト)の単なるインデックスです。

したがって、ポインタ/インデックスunsigned char index = 2がある場合、値は明らかに3番目の要素、つまり数値4になります。ポインタへのポインタは、その番号を取得し、それ自体をインデックスとして使用する場所ですRAM[RAM[index]]

私は紙のリストに配列を描き、それを使用して、同じメモリを指す多くのポインタ、ポインタ演算、ポインタからポインタなどを表示します。


1

私書箱番号。

それはあなたが何か他のものにアクセスすることを可能にする情報の一部です。

(私書箱の数値を計算すると、文字が間違った箱に入ってしまうため、問題が発生する可能性があります。また、誰かが転送アドレスなしで別の州に移動した場合は、ぶら下がりポインタがあります。一方、郵便局がメールを転送する場合、ポインターへのポインターがあります。)


1

イテレータを介してそれを把握するための悪い方法ではありません。

多くの元C ++開発者(イテレータが言語をダンプする前に最新のポインタであることを決して理解していなかった人)はC#にジャンプし、それでも適切なイテレータがあると信じています。

うーん、問題は、反復子のすべてが、実行時プラットフォーム(Java / CLR)が達成しようとしていること、つまり、新しくてシンプルな、誰でもが開発者であるという使用法に完全に対立していることです。これは良いことですが、紫色の本で一度言っただけでなく、Cの前でも前でも言っていました。

間接。

非常に強力なコンセプトですが、すべての方法を実行する場合はそうではありません。イテレータは、別の例として、アルゴリズムの抽象化に役立つため便利です。そして、コンパイル時は、非常に単純なアルゴリズムの場所です。あなたはコードとデータを知っていますか、または他の言語でC#:

IEnumerable + LINQ + Massive Framework =参照タイプのインスタンスのヒープを介してアプリをドラッグすると、お粗末なため、300MBのランタイムペナルティインダイレクション。

「ルポインターは安い。」


4
これは何と関係がありますか?
ニールウィリアムズ

...「静的リンクはこれまでで最高のこと」と「私が以前に学んだものと何が違うのか理解できない」とは別に、あなたは何を言おうとしていますか?
Luaan

Luaan、2000年にJITを分解して何を学べるかわからなかったでしょうか。2000年にASMでオンラインで示されているように、ポインタテーブルからジャンプテーブルになってしまうので、別のことを理解しないと別の意味を持つ可能性があります。注意深く読むことは不可欠なスキルです。
rama-jka toti 16

1

上記のいくつかの回答は、「ポインタは実際には難しい」ではないと主張しましたが、「ポインタが難しい」場所に直接対処することはしていません。から来た。数年前、私は最初の1年のCS学生を指導しました(私はそれをはっきりと吸ったので、1年間だけ)、理解。ポインター考え方は難しくないでした。難しいのは、いつ、いつポインタが必要になるのかを理解することです

幅広いソフトウェアエンジニアリングの問題を説明することから、その質問-いつ、いつポインターを使用するのか-を離脱することはできないと思います。なぜすべての変数がグローバル変数であってはならないのか、そして同様のコードを関数に分解しなければならないのはなぜですか(つまり、これを取得し、ポインターを使用して呼び出しサイトへの動作を特殊化します)。


0

ポインターについて何が混乱しているのかわかりません。それらはメモリ内の場所を指します。つまり、メモリアドレスを格納します。C / C ++では、ポインターが指すタイプを指定できます。例えば:

int* my_int_pointer;

my_int_pointerに、intを含む場所へのアドレスが含まれていると言います。

ポインターの問題は、ポインターがメモリ内の場所を指しているため、アクセスしてはならない場所に簡単に移動できることです。証明として、バッファーオーバーフロー(ポインターのインクリメント)からC / C ++アプリケーションの多数のセキュリティホールを見てください。割り当てられた境界を超えた)。


0

少し混乱させるために、ポインタではなくハンドルを使用する必要がある場合があります。ハンドルはポインターへのポインターであるため、バックエンドはヒープ内の断片化を解消するためにメモリ内で物事を移動できます。ポインターが途中で変化する場合、結果は予測できません。そのため、最初にハンドルをロックして、何もないことを確認する必要があります。

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5は、私よりも首尾一貫してそれについて話します。:-)


-1:ハンドルはポインターへのポインターではありません。それらはいかなる意味でもポインタではありません。それらを混同しないでください。
Ian Goldby 2012

「それらはいかなる意味でもポインタではない」-ええと、私は違いますようにお願いします。
SarekOfVulcan 2012

ポインタはメモリの場所です。ハンドルは任意の一意の識別子です。それはポインタかもしれませんが、配列のインデックスか、それ以外の何かかもしれません。あなたが与えたリンクは、ハンドルがポインタである特別なケースですが、そうである必要はありません。関連項目parashift.com/c++-faq-lite/references.html#faq-8.8
Ian Goldby

そのリンクは、それらがどのような意味でもポインターではないというあなたの主張もサポートしていません-「たとえば、ハンドルはFred **であり、Fred *ポインターが指すようになっている...」とは思わない-1はまあまあでした。
SarekOfVulcan 2012

0

すべてのC / C ++初心者は同じ問題を抱えており、その問題は「ポインターを学ぶのが難しい」からではなく、「だれがどのように説明されているのか」という理由で発生します。一部の学習者は、口頭で視覚的に収集し、それを説明する最良の方法は、「トレーニング」の例を使用することです(口頭および視覚的な例のスーツ)。

ここで、「機関車」は何も保持できないポインタであり、「ワゴン」は「機関車」がプル(またはポイント)しようとするものです。その後、「ワゴン」自体を分類し、動物、植物、または人(またはそれらの混合)を保持できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.