|
|
【目次】
|
.
|

|
【ポインタと変数の違い、渡し方の違い】 この文書では、変数やポインタを値とメモリが重なった 二層構造から見ていきます。 ![]() 変数には値、ポインタにはアドレスを入れて使います。
|
【型が一致しないとアドレスを渡せない】 C言語では: ・値 の 型(int,double,char等) ・データの種類(変数、配列、関数、ポインタ、構造体等) の両特徴が複雑に組み合っています。 そのため関数や変数に値を代入する場合、型が一致しないと渡せず、エラーになります。 (関数の場合は、引数の数や型の特徴も一致しなければいけません) 注意 誰かが作ったライブラリには、もとのC言語には存在しない独自のデータ「型」も存在します。 これらは、構造体やtypedef によって定義され、例えば BITMAP *bitmap や SAMPLE *sample 等がそれです。 見慣れない型を見つけたら、そのライブラリのドキュメントや、ヘッダファイルを見ると正体が分かることがあります。 |
【アドレスとは?】 どんな変数(関数、配列)でも、メモリ上のアドレス(番地) に、置かれています。 ポインタには、 他の変数のある場所(=アドレス) を入れることができます。 これを、ポインタの語源から、 「他のアドレスを指している」 と表現することができます。 ただし、値の型、データの種類を持つ変数のアドレスは、 同じ型のポインタに入れなければ、エラーになります、 【ポインタのデータの大きさ】 ポインタの1単位あたりのメモリサイズは、変数型によらず一定(4バイト)。 しかし、普通の変数は変数型(char, int ,double 等)ごとにサイズが違うため、ポインタ演算でポインタを1単位進ませても、アドレス値の進み具合も一定とは限りません。 (厳密にいえば、ポインタは、それぞれの変数の先頭アドレスを指すことになる。) |
|
|
![]() 以下、混乱を防ぐため「*」を間接参照子と統一して呼ぶことにします。 |
【たとえ】
ケータイに消防署のアドレスを登録しておけば、直接消防署に出向けない場合も助けを呼べます。 (C言語の世界では番号を長々と押さなくても、「&消防署」とするだけで自動的に番号が登録される) ケータイに消防署の番号を登録 【その他】
宣言しただけのポインタには、ランダムなゴミ値が入るので、必ず初期化しましょう。 【ポインタやアドレス渡しの使い道】
|




// この関数は結局、どのポインタを引数にとって、どのポインタ変数で上書き参照して返すんだろう?こういう時は、const修飾子を見ることで判断します。
int function(char *a , double *b, const char *c, const int *d){ }
int function(char *a , double *b, const char *c, const int *d){ }関数functionの仮引数のうち、ポインタ c 、 d は、const修飾子がついているので、参照可能だけど、上書き参照することはできません。
| 存在しない変数のアドレス参照 | 消滅したり、最初から存在しない変数のアドレスを、渡したり参照するとエラーになる。関数内で作ったローカル変数のアドレスは関数終了時に消滅するので、これを外で使おうとするとエラーになる。 |
|---|---|
| 未初期化のポインタを参照 | 宣言したばかりのポインタにはゴミデータ(ランダム値)が入るので、ここに値を上書き参照すると、意図せずシステム上のメモリのどこかが書き変わり、最悪の場合システムが破壊されます。 |
| 定数のアドレスを、 ポインタで読み書き |
ポインタに、本来書き換えない「定数」のアドレスを渡すと、参照時に上書きすることもできます。これは未知のトラブルを引き起こす可能性があります。(const修飾子) |
| はずれた場所まで読み書きする | 文字列定数の終端文字列を付けてないばかりに、プログラムが文字列の終わりを越えてどこまでもアドレスを読み続ける。他の領域まで間違って書き込んだりする。 |
|
これらの使い方は、配列や文字列のように連続して並んだ変数を扱う場合に用いられます。
forやwhileループと合わせることで、参照する場所を少しづつずらしながら、たくさんの変数に順番に走査させることもできます。 ただ、ポインタの *(p+1) のような使い方を見ると、必ずしもポインタが指し示す位置から変数をとってくるとは限りません。 今までのケースはたまたま、ポインタの指す場所と、値を取ってくる(または上書きする)場所が同じケースでした。 ポインタ変数自体はアドレスに付けた「めじるし」に過ぎないなら、 間接演算子こそ、指すアドレスの変数の内容を取ってくる役割を担うことになります。 ポインタと配列の違い: 配列名は、配列の先頭アドレスを指し、ポインタのように見えます。しかし、配列は先頭アドレスが固定されているので、 ポインタのように自由に指すアドレスを変更することはできません。 ポインタとリファレンスの違い 他言語のリファレンスでは、ポインタ演算のように、メモリ上の「変数a」の次(+1)にあるアドレスの内容を参照するといったことはできません。 ↓の記事を参考に 連続しない変数の場合 配列ではない変数を指す場合。 int a=5; int*p ; *p=&a;このとき *(p+1)と記述すると、ポインタから、メモリ上の「変数a」の次(+1)にあるアドレスの内容を無条件に参照しようとことができてしまう。 そのアドレスにはどんな情報があるのか無いのか、予想がつかない。この自由さは、メモリ上に変数がどのように割り当てられるかを理解しないまま扱ってしまうと危険だということを意味します。 他には、指す対象の配列の最大添字をオーバーした領域を指そうとする参照も危険です。(対象となる配列は10個の要素なのに ポインタを15単位進めた場所の要素を参照しようとする場合) |
| 表現型 | 参照先 | ポインタptrの 指すアドレス |
概要 |
| *ptr 型 | 移動しない | 移動しない | 一番シンプル |
|---|---|---|---|
| *(ptr+n) 型 | 移動する | 移動しない | ポインタの指すアドレスを基準にn単位移動させたアドレスを参照し、その変数の内容を取得。 |
| ptr[n] 型 | 移動する | 移動しない | *(ptr+n) の別の書き方。添字演算子[]で大量の変数を一括して扱うことを明示しますが、 配列とは別物。 |
| *ptr++型 | 移動する | 移動する | 参照先もポインタ自身も1単位移動。 便利な反面、配列終端を越えたデータまでアクセスする恐れもある。 ポインタを1単位移動させてから (インクリメント)、参照代入される。 |

/* int型の 束配列 */
int a[3][3]={
{0,1,2},
{3,4,5},
{6,7,8}
};
/* ポインタの束配列に、アドレスの配列を入れたので、OK */
int *aa[3][3]={
{ &a[0][0], &a[0][1], &a[0][2] },
{ &a[1][0], &a[1][1], &a[1][2] },
{ &a[2][0], &a[2][1], &a[2][2] }
};
/* ×ポインタの配列に、アドレスではない配列をいれて宣言したので、 エラーが起きる */
int *tn[3][3]={
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
/* ×今度は、 配列で、配列を束ねて宣言しようとして、エラーが起きる */
int bb1[3]= {0,1,2};
int bb2[3]= {0,1,3};
int bb3[3]= {0,1,2};
int aa[3]={ /* ←aaには配列の先頭アドレスは入らないので、間違い */
bb1,
bb2,
bb3,
};
printf("値を表示 %d /n", aa[2][1] );/* 間違い */
/* 上のような関連づけをしたいなら、ポインタ配列*aa で 配列bb1〜bb4を束ねる */
int bb1[3]= {0,1,2};
int bb2[3]= {0,1,3};
int bb3[3]= {0,1,2};
int *aa[3]={
bb1,
bb2,
bb3,
};
printf("値を表示 %d /n", aa[2][1] ); /* aa[] は ポインタを束ねた配列 */
/* ポインタ配列*aa→ポインタ配列*bb1〜*bb4 */
int *bb1[3]={ &a[0][0], &a[0][1], &a[0][2] };
int *bb2[3]={ &a[0][0], &a[0][1], &a[0][2] };
int *bb3[3]={ &a[2][0], &a[2][1], &a[2][2] };
int **aa[3]={ bb1, bb2 ,bb3 };
printf("アドレスを表示 %p /n", aa[2][1] );
/* 配列宣言の間違いによりエラー。なぜなら宣言時の配列の添字は、入れ物の最大値を意味するため。 */
int bb[0]={ 2 ,4, 6 }; /* bbの要素の[0]番目を配列で初期化 ←間違い */
int bb[1]={ 8, 7, 4 };
int bb[2]={ 7, 8, 9 };
/* これならOK */
int bb[3]={ 2 ,4, 6 }; /* 大きさ[3]の配列bbを用意して初期化 */
bb[1]= 10; /* bbの要素の1番目に 10入れる */
bb[2]= 10;
char aa[][64]={
"aaa",
"bbbbbbbb",
"ccccc"
};


#include "stdio.h"
//関数のプロトタイプ宣言
void ptrs(int **pp, int x , int y, int d[3][3] );
//引数は、 int d[3][3] の代わりに、配列へのポインタ int (*d)[3]でもOK
int main( void ){
///配列
int a[3][3]={ {0,1,2},
{3,4,5},
{6,7,8}};
int *p[3];
//ポインタを配列の先頭要素を指すようにする。
int i ; //C言語のfor関数のため
for( i = 0; i < 3 ;i++){
p[i]=a[i];
}
printf ("配列の先頭アドレス : %p \n", a );
ptrs(p,1,2,a);//引数は、ポインタ, 行、列、配列の先頭アドレス
printf ("変更後 : %d \n",a[1][2]);
return 0;
}
// 配列を渡す。 ポインタ配列 または 配列の先頭アドレス の場合
void ptrs(int **pp, int x , int y ,int d[3][3] ){
printf ("ダブルポインタ a[%d][%d] = *(*(pp+%d)+%d))= %d \n",x,y,x,y, *(*(pp+x)+y));
printf (" 〃 の別表記 pp[%d][%d] = %d \n",x,y, pp[x][y]);
printf ("そのアドレス : %p \n", *(pp+x)+y);
printf ("配列にアドレスを渡した d[%d][%d] = %d \n",x,y, d[x][y]);
printf ("その先頭アドレス : %p \n", d );
*(*(pp+x)+y)=50; // ポインタの
//pp[x][y]=39; // ポインタの別表記通じて編集
//d[x][y]=19; // 配列の先頭アドレスを通じて編集
print ("----------------------\n");
}
int *a[3]; /* 「int型の変数」 への ポインタ配列(入れ物の要素数は[3]) を 宣言 */
と
int (*a)[3];/* 「int型で要素数[3]を持つ配列」への ポインタ を 宣言。これが「配列へのポインタ」 */
//関数と配列の宣言。それぞれ↑の書き方、似ていませんか?
int function(int a){ return 0;} /* イ 引数(int a )を持つ関数 */
int dimension[3]={ 1,2 ,3 }/* ロ 要素数[3] を持つ配列 */
//関数へのポインタ と 配列へのポインタ
int (*pointer_of_function)( int a); // 引数( int a )を 持つ 関数へのポインタ。/* ハ */
int (*pointer_of_dimension)[3];//要素数[ 3 ] を持つ 配列へのポインタ。/* ニ */
//先ほどの2つのポインタと同じ種類であれば、アドレスを入れられます。
pointer_of_function = function;// 関数へのポインタ「ハ」に、関数「イ」の先頭アドレスを入れる
pointer_of_dimension = dimension;// 配列へのポインタ「ニ」に、配列「ロ」の先頭アドレスを入れる
void function( int *a ); はこのように、色々な似通った書き方で混乱しないためにも、
void function( int a[] ); と書けます
void function( int (*a)[3] ); は
void function( int a[][3] ); と書けます
func()

#include "stdio.h"
//関数のプロトタイプ宣言
int other(int a);
int other2(int a);
//関数へのポインタの配列 宣言
int (*spoint[2])(int b)={ other, other2};
typedef int (*ret)( int b);
ret FUNC(int a);
//メイン
int main( void ){
FUNC(0)(110);// 参照先の引数も書いた
return 0;
}
//関数へのポインタの配列を返す 関数
ret FUNC(int b){
return (ret) spoint[b];
}
//普通の関数
int other( int b){
printf("other1参照されたよ: %d \n",b);
return b;
};
int other2( int b){
printf("other2参照されたよ: %d \n",b);
return b;
};
float fb=20.0f; // float型の変数
float* ggg(){ return &fb; };//float型変数のアドレスを返す 関数ggg
//■複雑。
float*(*hhh[10])()={ ggg }; // gggのような関数を入れられる、関数へのポインタの配列hhh
float*(*( *iii() )[10])(){ return &hhh;};// 関数へのポインタ配列hhh を返す、関数iii
float*(*(*(*jjj)())[10])()=iii; // 関数iii へのポインタ
これを、typedefを使って型を定義すると、もっとシンプルに書けます。
float fb=20.0f;
float* ggg(){ return &fb; };
//■typedef を使って、すっきりまとめる
typedef float *(a_t)();
typedef a_t *b_t[10];
typedef b_t *(*c_t)();
a_t *kkk[10]={ ggg };
b_t* lll(){ return &kkk;};
c_t mmm=lll; /* 関数 lll のアドレスを入れられる */
次のような複雑な typedef 型定義も作れますが、やはりわかりにくい
typedef float*(*(*(*complex_t)())[10])(); complex_t nnn=lll; /* こちらも関数 lll のアドレスを入れられる */
typedef int ( * ( * ( *pointer ) (void ) ) [10] )(void );という宣言がある場合、これは 多重間接参照の一種( ***pointer) だと思われます 。
この関数の型 pointer は、typedef int ( * ( * ( *pointer )(void) )[10] )(void);

| 式 | ポインタの性質 | 指し先の値の変更(*p=100) | 指す先の変更(p=&var) |
|---|---|---|---|
| const int* p; | (*)参照先の値を変更できなくする | ×(読み取り専用) | ○ |
| int* const p; | 指すアドレスを変更できなくする | ○ | ×(読み取り専用) |
| const int* const p ; | 両方ともできなくする | ×(読み取り専用) | ×(読み取り専用) |
const → int* : const効果は、参照先にかかる:よって、間接参照子(*)を使っても参照先を変更できない【重要】ポインタに付けられた const修飾子は、関数の動作を把握する判断材料として、おおいに活用することができます。
const → p : const効果は、ポインタ自身にかかる:よって、 ポインタに他のアドレスを入れて変更できない
void main(){そこで関数のプロトタイプ(原型)宣言が役立ちます。この宣言は普通の関数の定義から、処理内容の{}を取り去ったものと同じで、 関数名と戻値の型、引数構造のみのシンプルな記述です。
int a,b;
a(a, b);/* 定義より先、 エラー */
}
/* 関数a の 定義 */
int a(int a,int b){ }


#include "stdio.h"
int main (void)
{
int *p; // pはint型変数へのポインタを宣言
// 宣言しただけの 未初期化ポインタが指すアドレスに 値を書き込む。 ※危険
*p=5; // どこか知らないアドレスに 5という値を書き込む。
//pが指しているアドレスの変数は何ですか?
printf("「p=&a;」でpの値は%dになります\n", *p);
return 0;
}
↑のコードをコンパイルしたものを実行した。