プログラミングC:ポインタ

この記事はシリーズの一部です – どのようにプログラミングするには:Cプログラミング

序文

これまでは、基本的な変数型とその前のCのプログラミングでの配列の拡張について説明しました。 このポストは、変数、値、メモリを扱う際の3番目の主要な要素であるポインタに焦点を当てています。 ポインタは、基本的に、データのメモリアドレスを保持する変数です。 Cは中間レベルの言語であるため、アセンブリ言語の残りの部分はまだ操作中に残っています。 つまり、PHPやPythonなどの高級言語では、変数の特定のメモリアドレスを扱わなければならないことはめったにありませんが、Cでは一般的な場所です。 ダイナミックメモリ割り当てをサポートするため、リンクされたリストのように異種のデータをリンクしたり、元のデータ構造を変更して関数に渡したりするために、ポインタはCで必要です。

残念なことに、ポインタはいくらか混乱し、さらに不幸になり、奇妙なエラーにつながります。 これは、コンピュータ全体のメモリ内の任意のアドレスを指す能力が非常に強力であるためです。 自分のプログラム内で初期化されていないポインタを使用し、独自のコード、スタック、場合によってはオペレーティングシステムのメモリ空間を上書きする可能性があります。

使用される適切なポインタは、メモリを扱い、配列のような連続した空間ブロックを扱う非常に便利な方法です。 ポインタがなければ、ヒープに動的なメモリブロックを割り当てることができません。 ポインタは関数を指すことさえでき、関数を他の関数に渡すことができ、実際には異なる機能を切り替えてプラグインすることができます。 この点に関して、Cは限定された関数プログラミング特性を有する。

ポインタとは何ですか?

ポインタは、プログラマからの直接値を格納するのではなく、メモリアドレスを格納する変数です。 このアドレスは、メモリブロック、関数、およびその他の変数を含む任意の数の物の位置です。 1つの変数に別の変数のアドレスが含まれている場合、最初の変数が2番目の変数または関数を指していると言われます。

上の図は、私が何を話しているかを示しています。 メモリアドレス0x005に位置する変数には値11が含まれます。ただし、メモリアドレス0x008にある変数には、最初の変数のメモリアドレスが格納されます。 矢印は、メモリアドレスが解決されたときに、0x005に格納された値をどのように指しているかを示します。 この点に関して、0x008に格納された変数はポインタであり、0x005に位置する値を指す。

ポインタを定義する方法

他のすべてのC言語と同様に、変数をポインタとして動作させるには、ポインタを先頭にして定義する必要があります。 これを行うには、基本データ型で説明した基本変数宣言を少し拡張する必要があります。 ポインタとなる型宣言があるときはいつでも、型指定の後にアスタリスクを入れます。 たとえば、以下のような場合、char型の変数を定義し、次にchar *型のポインタ変数を定義します:

注:ここから、コードスニペットにコメントを書くときに、C89コメント(/ *で始まり* /で終わる)を使用することがありますが、一般的に小さなコメントについてはC99の1行コメントシステム a //)

ポインタ変数は、技術的にはメモリ内の任意のアドレスを指すことができますが、その宣言には基本型が定義されていることに注意することが重要です。ポインタを定義するだけでなく、intポインタまたはcharポインタを定義します。これは、後でプログラミングでポインタの振る舞いを考えるときに非常に重要です。これは、すべてのポインタ操作が、すぐに説明するように、ポインタのタイプに関連しているためです。

voidポインタのようなものがあります。これは、一般的なポインタとも呼ばれます。このポインタは一般的な目的のポインタですが、強力ですが、ポインタ操作ではいくらか制限されています。それは何かを指し示すことを意図しています。何度も一時的にアドレスをメモリに保持して、より具体的な型のポインタにキャストすることができます。

はい、あるタイプのポインタは別のタイプのポインタに変換できます。これはポインタ変換と呼ばれます。ポインタの型が明白になるのは、まず第一に重要です。 intポインターにdoubleポインターをキャストし、そのintポインターを使って値を代入すると、doubleを得ることはできません。最初の4バイトだけを取得します:int。ポインタの割り当てでこれを詳しく調べます。

ポインタ演算子

これまでオペレータについては触れていませんが、単純に演算子は、任意の変数/データに対してアクションを実行するステートメント/シンボルです。 たとえば、2 + 5の文では、+は加算演算子であり、2つのオペランドを取ります.7の値を解決します。ポインタに対しては、2つの単項演算子があります。 メモリアドレス。 これらは*と&です。

&オペレーター

&演算子はかなり理解しやすいです。 それは、後に来る任意の式/変数を取り、そのメモリアドレスを返す単項演算子です。 私は個人的には&演算子を、現在私が扱っている変数の “アドレス”として読みます。 ポインタをそのアドレスに割り当てることができるように、変数をそのメモリアドレスに解決できることが重要です。 以下のコードを調べてください:

最後のステートメントで&演算子を使用しなかった場合は、ポインタに値7を代入します。 これは、ポインタがメモリアドレス7を指し示す原因となり、後でそれを使用する場合、重大なエラーが発生します。 つまり、&演算子を使用すると、myIntegerPionterにはmyIntegerVariableのメモリアドレスが含まれます。

*オペレーター

私の意見では、*演算子は理解するのが少し難しいです。 1つの理由は、ポインタ型宣言と同じシンボルを共有するためで、*演算子をポインタ型宣言(type *)と混ぜると少し奇妙なことが起こり得るからです。 もう一つの理由は、代入文の中の変数に*演算子を使うことができるからです。 私は説明します。

*演算子は、メモリアドレスを返す代わりに、ポインタが保持するメモリアドレスに格納されている値を返すという点で、&演算子と概念的に反対です。 これは、&演算子と同じ単項演算子です。 さらに例を上げると、上の例の後に次のように書くことができます。

この例では、anotherIntegerVariableに、最初は上記のmyIntegerVariableに格納されていた値が含まれています。 私は*演算子を “のアドレスで”と言って読むのが好きです。 正直言って、それは私にはちょっとしたものではなく、代わりにそれを単純に解決オペレーターとして想像しています。 どのメモリアドレスが格納されていても解決し、そのメモリアドレスにアクセスします。 私がこれを言う理由は、この種のコードが可能なためです:

あなたは最後の行を見ましたか? その行では、myPtrのメモリアドレスに格納された変数に番号7を割り当てました。この場合はmyVarです。 上記のブロックの実行が終了すると、myVarは7になります。そのため、コンパイラがその式をメモリアドレスに解決していることを示す* myPtrのようなものを想像しています。この場合、myVarと等価です。

ポインタの配列

ポインタは配列で定義することもできます。 通常の値と同じようにインデックスを作成できます。 たとえば、この簡単な宣言で10要素の配列を作成できます。 また、配列内の特定の要素に代入する例と、配列内の要素から解決する例も示します。

これは、動的に割り当てられた配列として知られている配列とは異なります。これについては、別の記事で説明します。

ポインターへのポインター

実際には、ポインタが別のポインタを指すようにすることは可能です。 これは複数の間接指定と呼ばれます。 これは、さまざまなメモリシステムとそれに対応する問題を扱うときに最も便利です。 ポインタへのポインタは、メモリアドレスの格納と解決の点で通常のオブジェクトへのポインタと変わりません。 ただし、その宣言はコンパイラの観点とは異なります。

2つのアスタリスクに注意してください。 これらは、これが別のポインタへのポインタであることを示します。 このコードは、ここで私が段落でできるよりも優れたアイデアを示すかもしれません:

ご覧のとおり、myMultipleIndirectionを割り当てるには、ポインタのアドレスにas(char **)を定義する必要があります。 これは、ポインタを指すポインタであるためです。 このタイプのインダイレクションは少し難解ですが、頻繁に使用されます。 ポインタは変数自身であり、独自のメモリアドレスも持つことができます。

ポインタの割り当て(変換)

上記のポインタ演算子の例では、基本的なポインタの割り当てを見てきました。 何かのメモリアドレスへのポインタを割り当てる場合は、&演算子を使用します。 ポインタに別のポインタと同じメモリアドレスが含まれているようにするには(追加の演算子を使用せずにポインタを別のポインタに割り当てるだけです)

異なるタイプのポインタを持っていても、同じものを指すようにしたいのですが? これはポインタの変換が行われる場所です。Cには “明示的キャスト”と呼ばれるメソッドがあります。これは、変数/ポインタを別の型のように見せて、コンパイラが型が一致しないと文句を付けないようにすることができます。 キャストを行うには、式の前にカッコを入れます。 だから、int値にcharポインタを割り当てましょう:

これは有効なコードです。 しかし、上記のように、charポインタは常にcharのように動作することを覚えておく必要があります。 1バイトのみを参照します。 myPtrを解決して別の変数を代入すると、charには1バイトしか格納されず、long intには4が格納されるため、整数myVariableの最初のバイトのみを取得します。

ポインタ演算

ああ、恐ろしいポインタの算術。通りの言葉が響くほど複雑ではありません。すべての操作は、指定されたポインタの基本型を基準にして行われることを覚えておいてください。まず、制約を定義しましょう。ポインタに対しては2つの操作しか実行できません。ポインタは加算と減算です。実際には、floatやdouble値ではなく、ポインタに整数を追加することしかできません。 2つのポインタを一緒に追加することはできませんが、それらを減算することはできます(私たちはそれに着きます)。

ポインタを増減するとどうなりますか?これは、ポインタの基本型に完全に依存します。しかし、ルールは、ポインタがベースタイプを含む可能性のある次のメモリアドレスを指し示すことになります。たとえば、charポインタをインクリメントすると、格納されているメモリアドレスを基準に次のバイトが取得されます。これは、charが1バイトを占めるからです。しかし、long intポインタをインクリメントすると、格納されているメモリアドレスに対して4バイトジャンプします。これは、long intが4バイトを占めるからです。次の図はこれを図式的に示しています。

(long int *)ポインタに1を加えるたびに、4バイト進めますが、(char *)ポインタに1を加えるたびに1バイト進めます。 これは、ポインタが基本型のサイズに従って進むためです。 ポインタの基本型が23バイトを占める場合、ポインタを1つ進めるたびにメモリ内の23バイト先を指すことになります。 ポインタの算術演算は、ポインタの基本型のサイズに対して相対的に発生します。

同じ基本タイプの別のポインタから1つのポインタを減算することができます。 これは、2つの間の基本型オブジェクトの数を返します。 つまり、別の(long int *)ポインタから1つ(long int *)を引くと、2つのメモリアドレスの間に存在するlong intの数が得られます。

ポインタの比較

1つのポインタと別のポインタを比較することは可能です。 下位のメモリアドレスを指すポインタが小さくなると表示されます。 これは、ポインタや配列、または同じオブジェクトや変数を指すポインタを扱う場合に便利です。 ポインターに関しては、さまざまな比較演算子(>、<、> =、<=)を使用できます。

結論

記事シリーズのこの時点では、痛みのように思えるかもしれませんが、明確なプログラムを構築するためには非常に強力です。 ポインタは、メモリ内のオブジェクトのアドレスを関数に渡し、動的に割り当てられたメモリを指し示すことを可能にします。 ポインタとその配列との関係、およびダイナミックメモリ割り当てとの関係は、シリーズに登場する記事の主題です。 今のところ、私たちのポインタは単にメモリ内の位置を指しています。 丸めとして、&演算子は変数のメモリアドレスを与え、*演算子は格納されたメモリアドレスを解決することに注意してください。 これらの2つの要素を念頭に置いて、ポインタを威嚇する必要はなく、実際には楽しいことができます。 読んでくれてありがとう、このチュートリアルから何かを学ぶのを助けてくれることを願っています!

この記事はシリーズの一部です – どのようにプログラミングするには:Cプログラミング

この記事を読んでいただければ、パトリオンのサポートを検討することもできます。

しかし、毎月の約束が少しでもあれば、私はそれを得る、あなたは私にコーヒーを買うことを考えるかもしれない。

photo credit: bmnnetwork isac-youtube via photopin (license)

あわせて読みたい

コメントを残す

%d人のブロガーが「いいね」をつけました。