忍者ブログ

Fグループ電子工作講座

秋月電子SH7125ボードで始めるマイコン開発

ポインタとは

ポインタとは変数の番地を示す変数です。
授業で習っても大半の人が理解できてないアレです。

私「ポインタって理解できてる?」
A氏「分かりますよ、変数の番地のやつですよね。」
私「じゃ、ポインタ使って関数作ってみて。」
A氏「できません。」
私「理解してないじゃん」
これを見栄と言います。なるほど、侍の国ですね。
勉強会を開くたびに毎度同じような会話が発生します。

ある範囲でプログラムを作成している限りではポインタは必要ありません。
ところが関数を作る、特に複数の変数に値を代入したり、配列を扱おうとすると必要になってくる概念です。
C言語で文字列の処理を処理をする場合避けて通ることができません。

見栄を張ってても仕方ないので勉強しましょう。



値渡しとアドレス渡し

一つの関数内でポインタを使ってもあまり意味がありません。
本当に効果が発生するのは関数を使う場合です。

予備知識として
int a;
とした場合
 a:変数cに入っている値を示します。
 &a:変数cが記憶されている場所を示します。
変数aが記憶されている場所を番地アドレスと呼びます。

int *c;
とした場合
 c:ある変数番地を示します。
 *c:ある変数入っている値を示します。
となります。

表の状況で
c=&d とすると、*cは67となります。

ここで、入力の2乗を出力する関数を考えます。

値渡しの例

int function_A(int x){
    int y;
    y=x*x;
    return y;
}

void main(void){
    int a,b;
    a=10;
    b=function_A(a);
}
<解説>
関数にaの中にあった数値である10が渡されます。
function_Aではaの中にあった数値10がxに格納されます。
以降、関数に渡されたaの中にあった値10はxとして処理されます。
yに計算結果が保存されます。
function_Aで処理した結果としてyが戻されて(return)います。
bはこの計算結果を受け取ります。
これは特に問題ないと思います。

アドレス渡しの例

int function_B(int* u){
    int v;
    v=(*u)*(*u);
    return v;
}

void main(void){
    int a,b;
    a=10;
    b=function_B(&a);
}

正直なところ、この処理でポインタを使う意味(利点)はありませんが、ポインタの意味を理解するため見てみましょう。
<解説>
関数にはaの番地が渡されます。
function_Bではaの番地がuに格納されます。
この状態でaの中身*uとして処理されます。
vに計算結果が保存されます。
function_Bで処理した結果としてvが戻されて(return)います。
bはこの計算結果を受け取ります。
 
実行結果はなにも変わりません。
ここで関数内をいじってみます。

int function_A(int x){
    int y;
    y=x*x;
    x=x+5;
    return y;
}
これをやってもmain関数内のaには何も影響がありません。


int function_B(int* u){
    int v;
    y=(*u)*(*u);
    *u=*u+5;
    return v;
}
これをやるとmain関数内のaの値が5増えます。

これが良いのか悪いのかは別にして、重要なのは値渡しだと「関数を実行した瞬間にaの中にあった数値」が渡されるのに対して、アドレス渡しだと「関数からaの中身を編集できるようになる」事です。


配列渡し

変数に設定された入力可能な1つの引数に対して渡すことができる値は1つのみです。
必要に応じて引数の数を増やしていけばある程度は対応できますが、文字列の様に大量に値があったり、渡したい変数の数が毎回変わるような状況には対応できません。
こういった場合には配列とポインタを使用します。

ここで配列のポインタ渡しをする前に配列を宣言した際のメモリ確保の動作を理解する必要があります。
char buf[10];
この様に宣言すると8bitで10個分のメモリが確保されます。
配列を宣言した際には以下の様にメモリが確保されます。

配列に値を入力する際は
buf[0]=42;
の様な書き方をします。
この様に描くと10個分確保されたメモリを個別に使用することができます。

変数に文字列を入力する際には以下の様な書き方をします。
sprintf(buf,"HELLO");
ここではbufの後にカッコがついていません
配列として宣言した変数にカッコを付けないとアドレス扱いになります。
これがまさに配列のポインタ渡しです。
この関数の目的も指定した配列の値を編集させることです。
配列にカッコを付けずアドレスとした場合、指し示す先は配列の先頭アドレス(1205)です。
sprintf関数はこの先頭アドレスから順に文字を格納していく関数です。

※ここで’¥0’=0x00は文字列がここまでである事を示す文字でnull(ヌル)文字等と呼ばれます。
 文字列を扱う際に重要な意味がありますが今は説明を省略します。


ポインタを使って関数に配列を渡すのは意外と簡単です。

void function_C(char data[10]){
    int i;
    for(i=0;i<10;i++){
        data[i]=i;
    }
}

void main(void){
    char buf[10];
    function_C(buf);
}

&やらやら要りません。
この場合は配列の中身を書き換えていますが、単に配列の中の値を関数内で使いたい場合も同じ方法で処理します。
画像など非常に大きいデータを扱う場合に値渡しをすると数値データの受け渡しを行うとそれだけで処理に時間がかかってしまいます。そういった大きなデータを扱いたい場合にもアドレス渡しは有効です。

C言語による配列のポインタ渡しで注意しなければいけないことは、渡すのはあくまで先頭アドレスのみで、配列数は関数に引き渡されないという事です。
コンパイラの仕様によっても変わるようですが、関数で使用する変数をdata[100]としてもビルド+実行が可能な場合があります。この場合、メイン関数で10個分しかメモリを確保していませんが、実行されてしまうと100個分のメモリを書き換えてしまいます。確保されていない90個分のメモリは何が格納されているかわかりません。何にも使用されていないといいのですが、重要なデータが格納されていると暴走してしまいます。
PR

コメント

プロフィール

HN:
ぼんどF博士
性別:
男性

最新コメント

[02/26 とし]
[02/25 ぼんどF博士]
[02/25 とし]
[10/25 ぼんどF博士]
[10/18 ss400]