こんにちは、ナナです。
「ポインタ」を学ぶ人がまずやるべきことは、「ポインタを使うこと」ではありません。
第1にやるべきことは「ポインタを知ること」です。
ポインタというものを、たくさん知ってあげてください。そうすることでポインタは皆さんの強力な武器となるのです。
本記事はポインタに対する「なぜなぜ」から、ポインタを知ることを目的にしています。
本記事では次の疑問点を解消する内容となっています。
では、「ポインタ」のことを知っていきましょう。
ポインタの全貌を学びたい方は『C言語 ポインタを使いこなせ【身に付けるための9の極意】』の記事から順に読むことをお勧めします。
なぜ、C言語にはポインタが存在するのか?
私は聞いたことがあるんです、こんな噂を。「ポインタの術はC言語にしかない、だからやめておけ」と。私が身に付けようとしているこの術は、身に付ける価値があるのですか?
ポインタがC言語系の言語にしかないというのはね、誤解なんですよ。「ポインタ」という機能の本質を知ると、この機能はあらゆる言語で必要な要素であると気づきます。
ポインタとはメモリ操作を行う本質的な機能です。この先どんなプログラミング言語を学ぼうとも、学ぶ価値は、はっきりと「ある」と私は答えますよ!
「JavaにはポインタがないからC言語よりわかりやすいよ」など、他の言語においてポインタというものがない、という話を聞いたことがある人もいるでしょう。
勘違いしないでほしいのです。どの言語においても「ポインタ」という機構は存在します。
その機構を、開発者に明確に公開しているのか、非公開にしているのかが違うのです。
非公開にできるのであれば非公開にすればいいじゃないか、そう思うかもしれません。
しかし、C言語は「ポインタ」という機構を開発者に公開することで、
他の言語にはないメモリに対する自由なアクセスを可能にした
のです。
この自由なアクセスは、組み込み開発では非常に重要なものです。組み込み開発においてC言語が現在でも主流であるのは、このポインタがあるおかげなのです。
なぜ、初心者はポインタを理解できないのか?
幾人もの先代たちが、この「ポインタの術」の前に朽ち果てたと聞いたことがあります。私は、私は越えられるのですか、この壁を!
コツがあるんでしょ、コツが!ちょっとでいいから教えてほしいんですよっ。
まず、世の中のC言語初心者がポインタを攻略できないことの原因として「メモリ」というものへの理解不足が挙げられるね。
「メモリを制する者が、ポインタを制する」んだよ。だから、「メモリ」を知ること、あえて言うならそれがコツだよ。
ポインタを理解する上で、必ず必要になる知識が「メモリ」です。「メモリ」を理解せずして、ポインタという機能は絶対に理解できません‼
皆さん、いいですかっ!
「ポインタ」を理解したければ「メモリ」を理解しろ!
これは絶対法則なんです。
メモリの特性を知ること
ポインタを知る前に「メモリ」に関してもっと興味を持ちましょう。
ここまで変数を使って当たり前のように利用してきたメモリですが、この「メモリ」を改めて理解しましょう。
メモリを理解する、といっても難しいことではありません。
メモリがそもそもできることは、数値を記憶し、取り出すことができるというシンプルなことです。
しかし、
このメモリへのアクセスができるためには、実はある条件を満たす必要がある
のです。
- メモリの場所が特定できること
- メモリにアクセスするサイズ・型が明確になっていること
このアクセス条件は、ポインタを理解するうえで非常に重要なルールとなります。
アクセス条件1:メモリの場所が特定できること
「メモリにアクセスしたい場所がわかっている必要がある」って当たり前のことですね。
とあるメモリに数値を読み書きしたいといっているのに、場所がわからなかったらできるわけがありません。
実際のプログラムにおいてどのように実現しているかを見てみましょう。
char number = 20;
number = 50;
このプログラムにおいてメモリの場所はどこか、といえば「number」が場所ですね。
本来メモリには、「番地」という一意に場所を特定するための情報があります。
しかし、100番地のメモリに「number」というラベルが付いていることで、ラベル自体が場所を特定できる印になっています。これが「number」でメモリを読み書きできる理由です。
このようにメモリにアクセスするためには、読み書きするための場所が特定できている必要があります。
アクセス条件2:メモリにアクセスするサイズ・型が明確になっていること
C言語では変数に対して「データ型」が紐づいていることは理解しているでしょう。
char型なら1バイト、short型なら2バイトなど、紐づいたデータ型に応じて予約されるメモリ量が変わります。
つまり、C言語では
メモリにアクセスする際に複数のメモリを使用するかを、開発者が選択する責務がある
ということです。
char a = 0x01;
short b = 0x2345;
long c = 0x6789abcd;
各変数のラベルは、先頭のメモリ番地と関連付いています。
そのため、メモリに読み書きする場合には、204番地から「4バイト分のメモリに対して値を読み書きする」といった、サイズ(型)の指定が必要になります。
このようにメモリにアクセスする際には場所以外にも、サイズ(型)がわからないとアクセスできないのです。
そのため変数を定義する際には、必ずデータ型を指定しなければなりません。それは、アクセスするメモリサイズを特定するためなのです。
このメモリへのアクセス条件こそが、ポインタを理解する鍵となります。後程でてきますので、皆さんしっかりと理解してください!
なぜ、ポインタを使えるとよいのか?
「ポインタの術」を発動すると、どんなことができるんですか?
この術を使って、どんなことができるようになるかを知りたいのですっ!
「ポインタの術」の効果だね。それはね、新しいメモリへのアクセス方法なんだよ。それを手に入れることで、開発の戦術の幅を広くできるんだよ。
ポインタは皆さんの新しい武器となります。どんな武器なのか特徴を示しましょう。
ポインタを使うとできること
「ポインタを使うと、いったい何ができるのですか?」
と聞かれれば
「ポインタを使うと、メモリの値を読み書きすることができます」
と答えます。
「えっ、それだけですか?」と思うかもしれませんが、そうなのです。メモリにアクセスできるだけです。
だからこそ「ポインタ」を難しく考えすぎないことです。
皆さんがすでに知っているメモリにアクセスする方法といえば「変数」を使うことですね。変数を定義すれば、メモリに対して自由に値を読み書きすることができます。
「ポインタ」は通常の変数からのアクセス方法とは異なる、新しいメモリアクセス方法を皆さんに提供してくれます。
このアクセス方法こそが、皆さんが身に付ける新しい武器になるんですよ。
ポインタによる新しいメモリアクセスのイメージ
まず、「ポインタ」をどのようなイメージで捉えるべきかを示しましょう。
ポインタのイメージとは「弓矢」です。
近接型の変数は、メモリアクセスの方法が直感的にわかりやすく扱いやすいのが特徴です。
それに対して遠距離型のポインタの特徴は、弓矢という「道具」と、扱うための「技能」が必要となります。それがポインタを扱うための技術なのです。
「ポインタの術」は弓矢を使いこなすことだったんですね。私は幼少より父上からあらゆる飛び道具の使い方を叩き込まれたので任せてください!
手裏剣、弓矢、名刺、あらゆるものを飛ばしてみせますっ!シュピッ
遠距離型のメモリアクセスって何?
「ポインタは遠距離型です」といっても遠距離って何?と思うことでしょう。ポインタのイメージをプログラムで表現してみましょう。
main関数内で定義されたローカル変数numberに対して「近距離型で0x01」「遠距離型で0x23」を書き込んでいるプログラムになります。
subfunc関数ではbowというポインタ変数(弓矢に相当する遠距離アクセスをするための道具)を作って、number変数に値を書き込んでいます。
本サイトでの「遠距離」とは、プログラムでいえば関数を跨いだ状態でメモリアクセスを行うことを表しています。
本来、ローカル変数というのは、定義対象の関数内からしかアクセスすることはできません。
しかし、
ポインタを利用することで、別の関数からローカル変数へアクセスすることが可能
となるのです。
関数を跨いでローカル変数へアクセスできる!これがポインタが持つ「遠距離型の武器」としての特徴なんです。
ポインタ変数の定義方法
ポインタを使いたい時は弓矢となる「ポインタ変数」が必要となります。作り方を覚えましょう。
定義の書式
データ型 * 変数ラベル名;
定義例
char * num;
ポインタ変数を作りたい時は特別な書き方が必要になります。それは、変数ラベル名の左に「*(アスタリスク)」を付けてあげることです。
ポインタの定義と格納するデータの特徴
ポインタ変数が管理するデータについて解説しておきましょう。
皆さんが知る「変数」とは、何かしらの数値を管理するためのものですが、ポインタ変数は管理するデータの種類が違います。
メモリには数値しか覚えられないので、メモリで管理されるのは数値であることは同じです。
ポインタ変数にはメモリの番地(アドレス/住所)の数値が記憶され、変数にはメモリの番地以外の数値が記憶されます。
メモリの番地以外の数値といっても「リンゴの数や金額などいろいろな数値があるよ」と思うかもしれません。しかし、それらは全て開発者が意味を付けているだけであり、コンピュータとしてはただの数値なのです。
そのため、C言語が意識するのは「番地の数値なのか、番地以外の数値なのか」なのです。
皆さんが番地という数値を変数に覚えておきたいと思ったならば、その数値はポインタ変数に記憶する必要があるのです。
「ポインタ変数にはどんな情報を記憶するの?」という問いには「メモリの番地です」と答えるのが正解です!
なぜ、ポインタは遠距離のメモリアクセスができるのか?
師匠!どうしてローカル変数は関数内だけしかアクセスできないのに、ポインタを使うと、なぜ別関数のローカル変数へアクセスできるのですか?
この「ポインタの術」の原理が知りたいのですっ!術とは原理を知ることで、さらなる高みへと到達できるのです!
そうだね。ポインタを使ったマジックにもちゃんとタネがあるんだね。このサイトを見た人だけに、特別にマジックのタネを教えちゃうよ!
なぜ、ポインタは関数を跨いで別関数で定義されたローカル変数へアクセスできるのでしょう。不思議ですよね。
ポインタ変数によるメモリアクセスの仕組みを解説します。
ポインタのメモリアクセスの秘密を公開します
もう一度学んだことを思い出しましょう。
メモリにアクセスするためには条件が2つ必要でした。
- メモリの場所が特定できること
- メモリにアクセスするサイズ・型が明確になっていること
「変数」と「ポインタ変数」では、条件1の場所の特定方法が異なります。
「変数」がラベルによりメモリ場所を特定するのに対し、「ポインタ変数」では番地によりメモリ場所を特定します。
メモリに対してユニークに存在する番地は、関数に依存せずメモリ場所を一意に特定することができます。これこそが遠距離アクセスが可能になる理由です。
numラベルが使用できないsubfunc関数では、メモリの番地を利用した遠距離アクセスでnum変数へのアクセスを可能にしているわけです。
これがポインタによる「遠距離メモリアクセス」のマジックのタネなんです。
ポインタ変数は、メモリの番地情報を利用して「遠距離のメモリアクセス」を可能にしているんですね!
マジックのタネを公開しちゃったけど訴えないでね…。
なぜ、ポインタによる遠距離アクセスはなぜ必要性なのか?そのメリットとは?
師匠!「ポインタの術」によって、別の関数のローカル変数にアクセスできることって、そんなに大事なことなんですか?その良さが全然わかりませんっ!
うん、うん、その良さがまだ伝わってないよね。別関数のローカル変数にアクセスできるって、ものすっごく便利なんだよ‼
これができなかったら、僕はまともにプログミングできないね。それくらい便利ってことだね。
そもそも、ポインタによる遠距離アクセスなんてものが、なぜ必要なのでしょう?
それは、C言語での関数におけるある制約が大きく関わっています。
関数の「引数」と「戻り値」の待遇の格差が生む確執とは
皆さん、関数において引数の数はいくつも定義できるのに、戻り値が1つのデータしか返せないことを疑問に感じたことはないですか?
関数というサービスから、複数の出力情報を呼び出し元に返却したいニーズってありそうですよね。
しかし、C言語では
戻り値は1つのデータ(正確には1つのデータ型)しか返せない仕様
なんです。
「関数から複数の情報を出力したい!」そんな時に「ポインタ」を使うのです。
具体的な、出力情報の増やし方に関しては、次の記事にお任せしましょう。
「ポインタ」を利用するケースで最も多いのが、関数からの出力情報を増やしたい時なんです。これができなかったら関数を作るのが大変なんです。