おぼえがき

過ちては則ち改むるにうんぬんかんぬん

ポインタ変数について(C言語)

f:id:feci:20210810151051j:plain

C11

 

C11におけるポインタ変数について。

#include <stdio.h>      //組み込み関数を使うために読み込み

void a(intintint);  //プロトタイプ宣言

void main() {
  int ia = 100;    //int型の変数
  int ib = 200;    //int型の変数
  int *ic = NULL;  //int型のポインタ変数(初期化はNULL)

 ic = &ia;        //icポインタ変数にia変数のアドレスを代入
  a(iaib*ic);  //100・200・100(*icはiaと同じ値になる)
  *ic = 300;       //icポインタ変数に値を代入
  a(iaib*ic);  //300・200・300(*icに再代入するとiaの値も再代入される)

 ic = &ib;        //icポインタ変数にib変数のアドレスを代入
  a(iaib*ic);  //300・200・200(*icはibと同じ値になる)
  *ic = 400;       //icポインタ変数に値を代入
  a(iaib*ic);  //300・400・400(*icに再代入するとiaの値も再代入される)
}

void a(int iaint ibint ic) {
  printf("ia  = %d\nib  = %d\n*ic = %d\n\n"iaibic);
}

ポインタ変数とは、ポインタ型(*)で宣言された変数のことです。

 

たとえば上記プログラムのように int *ic = NULL; と記述すると、値のない初期化済みのint型のポインタ変数を作ることができます(int* ic = NULL;でも可)。

ポインタ変数は、初期値が特に決まっていないときはNULLで初期化をするのが基本らしいです。

 

ポインタ変数は、「他の通常変数になりすますことができる変数」です。

もうちょっと具体的に言うと、ポインタ変数は代入されたアドレスと同じアドレスを持つ通常変数と同じ値をもつ参照渡し状態の変数として機能します。

 

たとえば上記プログラムのように、*icポインタ変数に&iaでia変数のアドレスを代入するとします。

そうすると*icポインタ変数は、ia変数と同じアドレスを持つia変数のクローンのような存在となります。

 

この状態になった*icポインタ変数は、値もNULLではなくia変数と同じ100という値をいつのまにか取得しています。

そしてこの状態の*icポインタ変数に100以外の別の値を再代入すると、ia変数の値もその値に勝手に再代入されてしまいます。

 

なおポインタ変数にアドレスを代入する際は、ポインタ変数名 = &変数名; のようにポインタ変数の先頭に*を付けずに記述します。

また他の変数のアドレスを取得してクローン化したポインタ変数に値を再代入する際は、*ポインタ変数 = 値; のように先頭に*を付けて記述します。

 

またポインタ変数をパラメータにする場合は、先頭に*なしの変数名だと「アドレス」を参照することになります。

*変数名をパラメータに指定すると、そのポインタ変数の「値」が参照されることになります。

 

 

Visual Studioのデバッガーで各変数のアドレスと値を見る方法について。

上記のポインタ変数のアドレスと値の変遷は、文章で見るよりも実際の動作状況を目で確認したほうが分かりやすいと思います。

 

もしVisual Studioを使っているのなら、デバッガーを使ってウォッチウィンドウを見ながらF11を押して各変数の値とアドレスの変遷を見るのがお勧めです。

 

以下、その方法の流れです。

 

①画面の左枠をクリックしてブレークポイントを設定する。
デバッグの開始ボタンをクリックする。
③画面上部のデバッグタグをクリック → ウィンドウ → ウォッチ → ウォッチ1をクリック。
④画面左下に出てきたウォッチリストに、アドレスを見たい変数と値を見たい変数の「&変数名」と「変数名」を入力する(ポインタ変数のアドレスは&〇〇で*は付けない)。
⑤F11を押しながら1ステートメントずつプログラムの処理を進めていく(関数やメソッドの呼び出し行でF10を押すと関数やメソッドへのステップインを飛ばせる)。

 

 

関数の引数をポインタ変数にするパターンについて。

#include <stdio.h>   //組み込み関数を使うために読み込み

void f(int*int*);  //プロトタイプ宣言

void main() {
  int ia = 100;      //int型の変数
  int ib = 200;      //int型の変数

 printf("ia = %d ・ ib = %d\n"iaib);  //100・200
  f(&ia&ib);
  printf("ia = %d ・ ib = %d\n"iaib);  //200・100
}

void f(int* aint *b) {  //*aにiaのアドレスを、*bにibのアドレスを代入
  int c = *a;             //cに*aのアドレスを代入(*aだとアドレス・aだと値)
  *a = *b;                //*aにibのアドレスを代入
  *b = c;                 //*bにiaのアドレスを代入
}

上記のプログラムでは、ユーザー定義関数の引数にポインタ変数を使うことで、そのパラメータに指定したmain関数内のia変数とib変数の値を相互に入れ替えています。

 

上記プログラムを見て分かるとおり、ポインタ変数の宣言にはデータ型の末尾に*を付けてもポインタ変数名の先頭に*を付けても、どっちの記述でもOKとなっています。

またこのパターンで注意しておきたいのが、f関数内にあるポインタ変数へのアドレスの代入の記述についてです。

 

f関数のa引数とb引数にはそれぞれ、ia変数のアドレスとib変数のアドレスが代入されています。

ここでは、ポインタ変数aとbの「値」の部分にia変数とib変数のアドレスが値として代入されています。

 

なので、ポインタ変数ではないc変数にia変数のアドレスである*aの値を代入することができます(値=値)し、aポインタ変数の「値」にbポインタ変数の値としてib変数のアドレスを代入できている、というわけです。

とてもややこしいですね。

 

ポインタ変数に代入する際には、「値=値」とするか「アドレス=アドレス」として、左辺と右辺をの関係を成り立たせないけません。

「値=アドレス」や「アドレス=値」の関係にすると、問答無用でErrorが出ます。

 

ご注意ください。

 

 

〇ポインタ変数と異常終了

ポインタ変数はぞんざいに扱うとプログラムを異常終了させてしまう原因となってしまうこともあります。

 

どういうパターンで異常終了の原因になってしまうかというと、宣言したポインタ変数に変数のアドレスを代入せずに値だけをNULL以外のものに再代入している場合がそれに該当します。

もしプログラム内にそのようなポインタ変数が1つでも存在していた場合は、そのポインタ変数を参照する処理があるかないかに関わらず、そのプログラムを実行した数秒後にプログラムが異常終了してしまいます。

 

…と講座の内容ではこうなっていたのですが、自分の環境で実際に試してみたところ、この条件でプログラムが強制終了するということはありませんでした。

ただデバッグをするとErrorが出てプログラムを実行することはできませんし、ソース画面でもすでに警告が出ている感じではあります。

 

まあなんかそんな感じです。

 

 

以上です。