ポインタ
C言語の学習をされている方で、つまづきやすいところの一つに「ポインタ」があると思います。私は学生の頃、ポインタが理解できずにC言語を一度挫折した経験があります。
しかし以前受講した組み込みソフトの研修で、「malloc」と「free」という関数に出会うことであっさり理解できました。
私自身の理解をまとめてみましたので、ご参考頂ければと思います。
「ポインタとは何か?」を一言で言ってしまうと次の通りとなります。
結論:ポインタとはC言語で変数を参照渡しするときに使うもの
よろしければこちらの記事も参照して頂ければと思います。「C言語のポインタとは結局何なのか? 〜ポインタの使い方〜」
C言語の概念
C言語の概念ですが、「関数」という考え方がベースになっております。
もちろんPHPやJavaScriptなど他の言語も関数がありますが、C言語における関数はもう少し厳密な形となっております。
ここでそもそも「関数」とは何か?を少しご説明します。
関数とは : 入力値に対して出力値が一つに決まるもの (入力値は1つでも複数でも0でも構いません。)
ここで少しクイズを出します。以下の3つのうち出力値「y」が入力値「x」の関数になっているものはどれでしょう?
- y=x2+1
- y=3
- x2+y2=10
x=1を代入してみます。
1は普通に「y=2」となり、出力値が一つに決まります。
2は代入するxがありませんが、出力値が「y=3」と一つに決まります。
3はx=1を代入すると、「y=+3, -3」と2つの値が出力されます。
従いまして、正解は1と2になります。3は関数ではありません。
C言語において入力値は「引数」、出力は「戻り値」となりますので、言い換えますと下記の通りとなります。
C言語の関数とは : 引数に対して戻り値が一つに決まるもの (戻り値は1つでも複数でも0でも構いません。)
ここで重要なことですが、C言語は戻り値が一つだけになるということです。つまり「C言語の関数は一つの値しか戻り値を返すことができない」ということになります。
C言語の問題点
「一つの値しか戻り値を返すことができない」ということから、なんとなくC言語の問題点はご想像いただけるかと思いますが、一応説明させてください。
例えば下記のように加算値と減算値を返す関数を作ろうとします。
int add_and_sub(int x, int y){ //加算と減算をする関数
int add;
int sub;
add = x + y;
sub = x - y;
return (add,sub); //エラーになる!
}
戻り値は一つだけですので、「add」か「sub」のどちらかしか返すことができません。従いまして、当然ですがreturnの中に2つの値を入れるとエラーになります。
これを解消する方法ですが、そもそも関数は機能別に作るのが基本ですので、加算と減算でそれぞれ関数を作ればOKです。
int add(int x, int y){ //加算する関数
int add;
add = x + y;
return (add);
}
int sub(int x, int y){ //減算する関数
int sub;
sub = x - y;
return (sub);
}
今度は簡単なソート機能を実装した関数を考えてみます。3つの変数を入力してそれを昇順に並べて返す関数を作ろうとします。
int sort(int x, int y, int z){
int number[3],tmp;
number[0]=x;
number[1]=y;
number[2]=z;
/* 数値を昇順にソート */
for (i=0; i<3; ++i) {
for (j=i+1; j<3; ++j) {
if (number[i] > number[j]) {
tmp = number[i];
number[i] = number[j];
number[j] = tmp;
}
}
}
/* return(number[0], number[1], number[2]); この記述はエラーになる。 */
return(number[0]); // 一つなら返せる。
}
この場合、配列全部の値を戻り値として返したいのですが、前述の通り戻り値は一つだけですので、配列「number」の要素の一つしか返すことができません。
同じように「文字列」も返すことができないということになります。
つまりC言語の問題点は「配列を戻り値として返すことができない」ということになります。
そこで参照渡しとポインタの出番となります。
参照渡し
PHPなど他の言語を学習されている方でしたらご存知かもしれませんが、簡単にご説明します。
変数に変数を代入する場合(例:y=xなど)ですが、2種類のやり方があります。
- 値のよる代入 → 値渡し
- 参照による代入 → 参照渡し
PHPで例を示していきます。(スクリプティングデリミタ「<?php」は省略させて頂いておりますのでご了承ください。)
まずは値渡しの例です。
$x = 1;
$y = $x; // $xの「値」を$yにコピー
$x = 5; // $xの値を変更
print $y; // 表示結果:1
こちらは特に問題ないかと思います。当然ですが$xの変更は$yには影響しません。
次に参照渡しの例です。参照渡しは変数のアドレスをコピーします。
$x = 1;
$y = &$x; // $xの「アドレス」を$yにコピー
$x = 5; // $xの値を変更
print $y; // 表示結果:5 → $xの変更が$yにも反映される
こちらは$xの変更が$yにも影響しています。(逆に$yを変更した場合、$xは影響を受けます。)
$xも$yも参照しているメモリアドレスが同じため、こういった結果になります。これが参照渡しです。
ポインタによる参照渡し
「配列全部の値を関数からもらうにはどうしたらよいか?」という問題ですが、「参照渡しをしてあげればOK」ということになります。
こちらは別記事「C言語のポインタとは結局何なのか? 〜ポインタの使い方〜」にも書いてみましたのでそちらも参照して頂ければと思います。
先程のコードは下記のように修正することができます。
int sort(int x, int y, int z, int *number){
// ポインタで呼び出し元の変数のアドレスをもらう
int tmp;
number[0]=x;
number[1]=y;
number[2]=z;
/* 数値を昇順にソート */
// 呼び出し元の変数を直接書き換えている
for (i=0; i<3; ++i) {
for (j=i+1; j<3; ++j) {
if (number[i] > number[j]) {
tmp = number[i];
number[i] = number[j];
number[j] = tmp;
}
}
}
return; // 戻り値はなし。
}
呼び出し元で宣言した変数のアドレスをもらっていますので、これは呼び出し元が変数「number」に参照渡しをしていることになります。
「number = 呼び出し元の変数のアドレス」 → 参照渡しをしている
従いまして、sort関数で変更した内容が呼び出し元の変数にも影響するようになります。
「配列を戻り値で返す」という考え方ではなく、「配列を参照渡しして直接書いてもらう」という考え方になります。
この参照渡しをするときにポインタが必要になります。つまりC言語のポインタは参照渡しをするために存在していると考えることができます。
ポインタがある理由:C言語で参照渡しをするため
まとめ
「C言語のポインタとは結局何なのか?〜ポインタがある理由〜」についてまとめてみました。
ただしポインタがある理由につきましては、あくまで私個人の考え方となりますのでそこはご了承ください。
よろしければこちらの記事も参照して頂ければと思います。「C言語のポインタとは結局何なのか? 〜ポインタの使い方〜」