ポインタと動的メモリ確保
C言語勉強会 第十一回
kumar
September 24, 2013
引用 : Programming Place Plus
C言語勉強会 第十一回
kumar
September 24, 2013
引用 : Programming Place Plus
参照先のメンバにアクセスするには、.ではなく、 アロー演算子-> を使う。
構造体へのポインタ->メンバ名
typedef struct {
char name[20];
float height;
float weight;
} Human;
/* Human構造体のポインタを引数に取る関数 */
void printHuman(Human *p) {
printf(" 名前: %s", p->name);
printf(" 身長: %f", p->height);
printf(" 体重: %f¥n", p->weight);
}
指定した変数が定数であることを指定する。
定数とは、初期化出来ても書き換え出来ない変数のことである。文法
const 型名 変数名; または 型名 const 変数名;
これに対して、constポインタはポインタ自身でなく、参照先の書き換えを禁じる。
そのポインタから参照先を書き換えることを禁じる
const 型 *変数名
または
型 const *変数名
int 型変数 x が宣言されているとする。
int *pointer = &x; /* ポインタを宣言 */
const int *const_pointer = &x; /* constポインタを宣言 */
int bar = 10 + *pointer; /* 可能 */
int foo = 10 + *const_pointer; /* 可能 */
*pointer = 100; /* 可能 */
*const_pointer = 100; /* 不可能。コンパイルエラー */
ミスが減る。 特に配列を(ポインタにして)引数にとる関数を書く際に、配列を誤って書き換えることがない
constポインタを引数にとる関数の例
int strlen(const char *str) {
int len = 0;
while(str++ != '¥0')
len++;
return len;
}
インクリメントやデクリメントは、演算子を変数名の前に書くか後ろに書くかで名前、性質が変わる。
iを何らかの変数とする。
| ++i | 前置インクリメント |
| i++ | 後置インクリメント |
御存知の通り、インクリメントは「変数に1足す」という意味で、デクリメントは「変数から1引く」という意味である。だが、演算子が前置か後置かで、式の中で演算が行われるタイミングが異なる。
式の評価より先に インクリメント、またはデクリメントを行う。
式の評価の後で インクリメント、またはデクリメントを行う。
int n, i = 0;
n = i++; // n = 0, i = 1
i = 0;
n = ++i; // n = 1, i = 1
ちなみに、前置の方が後置より微妙に高速である。すごいプログラマは前置と後置、どちらを使っても良い状況で、前置を好む傾向にある。(気がする)
size_t i;
for(i = 0; i < 100; ++i)
{
unko unko unko...
}
こんなかんじです。
ポインタを一行で一度に宣言するとき、
int *x, y, z;
と書きたくなるかと思うが、こう書くと、xだけがポインタになり、yとzは通常の変数になってしまう。正しくは
int *x, *y, *z;
こう書くのが正しい
先の例で、構造体をわざわざ関数にポインタ渡ししていたが、ポインタを使わず普通に渡してもいいのでは?(普通に渡すことを、 値渡し という)と思った人も居るかもしれない。
/* Human構造体を値渡しで引数に取る関数 */
void printHumanValue(Human v) {
printf(" 名前: %s", v.name);
printf(" 身長: %f", v.height);
printf(" 体重: %f¥n", v.weight);
}
/* Human構造体のポインタを引数に取る関数 */
void printHumanPointer(Human *p) {
printf(" 名前: %s", p->name);
printf(" 身長: %f", p->height);
printf(" 体重: %f¥n", p->weight);
}
だが、値渡しより、 ポインタ渡しの方が高速 である。何故なら 値渡しでは構造体のメンバを全てコピーして渡す のに対して、 ポインタ渡しでは、ポインタのみをコピーして渡す だけであるからだ。これは32bit環境ならたったの4バイトであり、構造体がいくら巨大であろうと固定である。
しつこい話をする。
簡単のため省略していたが、
void printHumanPointer(Human *p);
は、 p の参照先のメンバを画面に出力するだけで、書き換えるつもりのない関数だ。だから、 p は const 修飾すべきだ。
void printHumanPointer(const Human *p);
アロー演算子も配列の添字と同じく、糖衣構文(シンタックスシュガー)である。
ps->x
におけるアロー演算子は
(*ps).x
の糖衣構文(シンタックスシュガー)である
以下の例はconstポインタの宣言の文法である
const int *x;
int const *x;
だが以下の例はそうではない。
int *const x:
静的メモリ確保とは、今まで扱ってきた、配列宣言のこと。
int arr[ 10 ];
実は以下の例はC言語では動かないのだ。(例外有り。後述)
int n = ?; // ? は任意の数字
int arr[ n ]; // コンパイルエラー
何故なら、配列の要素数は定数で無くては宣言できないからだ。言い換えると、 コンパイルするタイミングで、配列の長さが決定されている必要がある 。 これが静的メモリ確保だ。 この欠点を補うのが動的メモリ確保である。
実行時にメモリ領域の確保を行うこと。
今までの静的メモリとは違い、 実行時に自由な大きさの配列を作ることができる。
なお、以降の関数を利用するためには、標準ライブラリstdlib.hがインクルードされている必要がある。これを忘れた場合、なんとコンパイルエラーとはならず、本来とは違う挙動を行うことがあるので要注意である。
配列の宣言とは全く違い、関数を使って確保を行う。 直感的でないのだが、古い言語なので我慢して欲しい。
動的メモリ確保を担う関数は以下のとおりだ。
確保したメモリを解放する関数は以下のとおりだ。
void *malloc(size_t size);
size バイト分の連続したメモリ領域を確保し、その領域へのポインタを返す。
エラー時にはNULLを返す。
この関数はvoidポインターが戻り値である。voidポインターは汎用ポインタと呼ばれ、どの型にも勝手にキャストされる。
int *p = malloc( 10 * sizeof(int) );
要素数10のint型配列を作った。 32bit環境ではsizeof(int)は4なので、mallocに 40 を渡して、 40バイト分、メモリを確保した。
簡単のため要素数は10にしたが、このように要素数が定数の場合は静的メモリ確保でいいと思う。 実際には定数でなく変数を渡すのが普通だ。
そして、このようにメモリを確保したら必ず、必ず、次に説明するfreeで、確保したメモリ領域を開放し無くてはならない。
void free(void *pointer)
pointerの指すメモリ領域を開放する。
malloc等で動的確保したメモリは、この関数を使わない限り、プログラムが終了するまで開放されない。メモリの無駄遣いを防ぐため、 必ず使い終わったメモリはこの関数で開放すること 。メモリの解放をし忘れることを、 メモリリーク と呼び、大量にメモリリークすると、システムの全てのメモリを食いつぶしてしまい、 最悪OSを巻き込んで停止してしまう。
長さ ? のint型配列を作る。
size_t n = ?;
/* 動的メモリ確保する */
int *p = malloc( n * sizeof(int) );
/* 配列と同じように使える */
p[0] = 100;
printf("%d¥n", p[0]);
/* 使い終わったら */
free( p );
void *calloc(size_t nelements, size_t bytes)
bytesのサイズのメモリ領域をnelements個格納できるメモリ領域を確保する。
これは以下の様な解釈で問題ない。
void *calloc(size_t nelements, size_t bytes) {
return malloc(nelements * bytes);
}
当然、mallocと同じくfreeで開放し無くてはならない。
void *realloc(void *pointer, size_t bytes)
pointerが指すメモリブロックをリサイズする。
具体的には、bytesバイトのメモリ領域を新たに確保して、pointerメモリブロックにあったデータはできる限りコピーする。
以前より大きくすることも、小さくすることも可能である。
当然、mallocと同じくfreeで開放し無くてはならない。
{
int n = ?;
int arr[ n ]; // C99以降のみ可能
unko unko unko...
} // ブロックを抜けた時に自動で解放してくれる
このようにできます。しかもfreeを呼ぶ必要なし。freeし忘れるミスを減らせるので積極的に使っていけ
でもmallocちゃん達もまだまだ現役なので、ちゃんと覚えてね。