• 検索結果がありません。

02: 変数と標準入出力

N/A
N/A
Protected

Academic year: 2021

シェア "02: 変数と標準入出力"

Copied!
26
0
0

読み込み中.... (全文を見る)

全文

(1)

11: 動的メモリ確保

Linux にログインし、以下の講義ページを開いておく

こと

http://www-it.sci.waseda.ac.jp/teachers/w48369

2/CPR1/

C プログラミング入門

基幹

7 (水5)

1

2017-06-28

(2)

まとめ:ポインタを使った処理

内容

説明

呼び出し元の変数を書き換える 第 9 回

文字列を渡す・配列を渡す

第 10 回

ファイルポインタ

第 10 回

複数の値を返す

今回

大きな領域を確保する

今回

2

(3)

ポインタの扱い

(4)

複数の値を返す

return は 1 つの値しか返せない

複数返すには、書きこんでもらいたい変数へのポインタを渡

例:int 配列の最大・最小値を計算する

int maximum(const int *a, int n);

int minimum(const int *a, int n);

void

minmax(const int *a, int n,

int *

m,

int *

M);

4

最大値を書き込むアドレス (変数などへのポインタ) 最小値を書き込むアドレス (変数などへのポインタ) 書き換えるので、 const が つかない 整数の配列の先頭ポインタとその個数 返り値を使って計算 返り値はなし

(5)

標準ライブラリ関数の例

整数部と小数部を求める関数

double modf(double value, double *iptr);

戻り値:value の小数部(符号付き)

iptr の指すメモリ領域:整数部(符号付き)

5

#include <stdio.h> #include <math.h> int main(void) {

double ipart, fpart;

fpart = modf(32.5, &ipart);

32 double ipart 変数 ipart のアドレスを渡 すことで、関数 modf は、 ipart の中身を書き換える ことができる 小数部 0.5 は戻り値として返る 0.5 double fpart

(6)

NULLポインタ

(7)

ポインタ変数の初期値の注意

通常の変数同様、ポインタ変数も初期化されない

しかし、どこかは指している

初期化しないポインタ変数でアクセスすると危険

7

{ int *p; // 初期化なし *p = 100; p

暗黙の初期化はされな いので、どこを指すア ドレスが入っているか は不明。

(8)

NULL ポインタによる安全策

以下の場合、 null ポインタを入れるとよい

初期化ではアドレスを決定しない

今まで使っていたアドレス無効になった

null ポインタを通したアクセスは発見しやすい

8

{ int *p = NULL; *p = 100;

p null ポインタが指す領域にアクセスすると、 システムが検知して、例外を発生する ポインタ変数の値(アドレス) が null ポインタの場合、斜線 を引いて、どこも指示していな いことを表現することがおおい。

(9)

null ポインタ

どのアドレスでもないことを示す特別な値

空ポインタともいう

null ポインタに対してデリファレンス演算子

*

を使うと例外

(null pointer exception)

が発生する

null ポインタ定数マクロ

NULL

を使って判定する

数値リテラル 0 は、 null ポインタに変換される

9

<stddef.h> で定義されているが、 <stdio.h> などで自動的にインクルードされる

(10)

null ポインタ判定

以下のような書き方のバリエーションがある

10

p が null であるか

p が null で

ない

if(p ==

NULL

)

if(p != NULL)

if(p ==

0

)

if(p != 0)

if(

!

p)

if(p)

if は常に式の評価値が0でないことを判定する ので、上段の式と同じ意味になる。

(11)

メモリ領域を作成する

(12)

基本型を返す関数

12

...

// 行列のトレースを返す関数

double trace3(const double Mat[3][3])

{ double tr = Mat[0][0]+Mat[1][1]+Mat[2][2]; return tr; } int main(void) { double Mat[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; trace3(Mat); // => 15 double 型などの基本 型を戻り値とする関数 は簡単に作れる 値そのものがコピーを されて伝搬していく このような、配列サイズが固定の 場合は、大きさを仮引数に書くこ とができる。ただし、単に先頭ア ドレスが渡されてくることには変 わらない。 ここで、配列のアドレス計算は Mat[i][j] = *(Mat + i*3 + j) で あり、2番目のサイズのみが使われ るので、 Mat[3][3] という宣言は Mat[][3] と書いてもよく、1番目 の値は単に無視される。

(13)

配列を返す関数?

13

...

// a から始まる 3 要素の連続値を返す

int* seq3(int a) {

int s[] = { a, a+1, a+2 };

return s;

}

int main(void) {

int *seq = seq3(10); seq[0]; // => 10? seq[1]; // => 11? seq[2]; // => 12? 配列の先頭へのポイン タを返す? 配列の先頭へのポイン タを受け取る?

この方法ではうまくいかない

配列のコピーは できないので… 関数の中で用意した配列

(14)

関数内の配列のアドレスを返してはいけない

関数内で定義された配列変数は、通常の変数と同様に関数の

実行が終了する際に消滅する

14

...

unsigned char *createNewImage(void) {

unsigned char img[512*512] = { 0 };

return img;

}

int main(void) {

unsigned char *pImage;

... pImage = createNewImage(); // この後ポインタ pImage を使って // アクセスしてはいけない ... 配列の型を戻り値とする関数 は作れない その代わり、ポインタを返す このポインタ(アドレス)の 指す領域は値が返った時点で すでに無い pImage createNewImage の領域 img この領域は関数の実行後、 消滅してしまう

(15)

自動的に消滅しないメモリ領域を作る

動的メモリ確保

(dynamic memory allocation)

変数のような言語機能ではなく、標準ライブラリ関数を使っ

て、直接 OS にメモリ領域を要求する

確保された領域は自由に使える

変数名はつかないので、ポインタでアクセスする

自動的には消去されないので、自分で解放する

15

(16)

動的メモリの確保

malloc() 関数によって指定したバイト数のメモリ領域を確保

する

メモリ領域は初期化されない

メモリ不足などで確保が失敗した場合、 NULL ポインタが返

16

#include <stdlib.h>

void *malloc(size_t size); malloc() のプロトタイプ

次のスライドで説明

確保された領域が置かれ ているところをヒープ (heap) という

(17)

ヒープ領域

確保されたメモリ領域の値

malloc は

void *

型を返す

特定の型を表さない

任意のポインタ型に自動変換できる

17

C++では自動変換されない ため、 static_cast が必要 { int *pInt; double *pDouble; pInt = malloc(16); pInt[0] = 100; // double の場合 // pDouble = malloc(16); // pDouble[0] = 3.14; main のスタック領域 ? ? ? ? ? ? ? ? ? ? ? ? ? ? sizeof(int) == 4, sizeof(double) == 8 の場合 3.14 100 指し示すアドレスは同じだが、 扱われ方が型によって異なる

(18)

サイズの指定

例: int 型の100個の領域を確保

int *mem = malloc(sizeof(int) * 100);

mem[0] ~ mem[99] としてアクセス

(19)

サイズの指定

例:画素値を unsigned char 型として、 w × h のサイズの

メモリ領域を確保

unsigned char *image =

malloc(sizeof(unsigned char) * w * h);

座標 x, y に対して、 image[x+y*w] としてアクセス

0 ≤ x < w, 0 ≤ y < h

19

sizeof(unsigned char) は必ず 1 なので、書かなくてもよい

w

h

image[0+0*w] image[x+y*w]

(20)

動的メモリの確保 (その他)

calloc(): malloc() と同じだが、さらにゼロで初期化を行う

size × num バイトの領域を確保する

メモリの全てのビットが 0 となるからと言って、 double

などの型の値として 0 となるとは限らない

realloc(): 確保された領域のサイズを変更

20

#include <stdlib.h>

void *calloc(size_t num, size_t size);

void *realloc(void *ptr, size_t new_size);

(21)

動的メモリの解放

確保したメモリへのポインタを指定して、その領域を解放す

解放した領域は使用してはならない

NULL を入れておくと安全。

既に解放済みのポインタに対して実行してはいけない(2重

解放エラー)

NULL ポインタを与えた場合は何もしない

21

#include <stdlib.h> void free(void* ptr); free() のプロトタイプ どんな型のポインタも void * に 自動的に変換される

(22)

例題:PGM 画像

画像のためのメモリを自動的に確保する関数

22

...

// 画素値 0 で初期化された画像を動的に作成する

unsigned char *createImage(int width, int height)

{

int i;

unsigned char *img = malloc(width * height); if(img == NULL)

{

return NULL;

}

for(i = 0; i < width*height; ++i) img[i] = 0; return img; } sizeof(unsigned char) == 1 な ので、掛けるのを省略 メモリ不足の場合は NULL を返す すべてゼロで初期化する

(23)

例題:PGM 画像

使い終わったら自分で free() する

23

... int main(void) {

unsigned char *Image = NULL;

...

Image = createImage(640, 480);

...

free(Image), Image = NULL;

ポインタは無効になるので、 NULL ポイン タを代入しておくとよい

(24)

用語:メモリリーク (memory leak)

malloc() で動的確保した領域を free() で解放し忘れて、メ

モリが圧迫されること

単純なプログラムではこの手のバグは見つけやすいが、秋期

「Cプログラミング」で扱う複雑なデータ構造では発生しや

すく、見つけづらいバグとなる。

24

(25)

変数とポインタと動的メモリ確保の整理

メモリに領域を確保して値を読み書きする方法には4つある

25

分類 生存期間 スコープ メモリ領域 初期化 自動変数 (ローカル変数) 定義位置から ブロック終端 まで 定義位置か らブロック 終端まで スタック 初期化が指定されている場 合のみ、ブロックに入るた びに初期化される 大域変数 (グローバル変数) プログラムの 実行開始から 終了まで 定義位置か らプログラ ム終了まで 静的領域 プログラム開始時に1度だ け。初期化が指定されない 場合、0 で初期化される 静的変数 (static 変数) プログラムの 実行開始から 終了まで 定義位置か らブロック 終端まで 静的領域 同上 動的メモリ 確保から解放 まで ヒープ malloc() はされない calloc() は 0 を書きこむ 変数名で参照しない

(26)

配列変数と動的メモリ確保の比較

機能 配列 動的メモリ確保 サイズ 自動変数の場合は、あまり大きな サイズは確保できない。 ほとんど制限がない 多次元 可能。ただし、関数などに次元や サイズを伝えられない 単なるポインタなので不可能 コピー 不可能。標準ライブラリ関数を利 用 不可能。標準ライブラリ関数を利用 解放 自動変数の場合、ブロックを抜け ると自動的に消滅する free() によって自分で解放する アクセス 変数名が先頭へのポインタになる malloc() によって得られるアドレ スをポインタ変数に入れて扱う

26

参照

関連したドキュメント

が前スライドの (i)-(iii) を満たすとする.このとき,以下の3つの公理を 満たす整数を に対する degree ( 次数 ) といい, と書く..

ある周波数帯域を時間軸方向で複数に分割し,各時分割された周波数帯域をタイムスロット

・逆解析は,GA(遺伝的アルゴリズム)を用い,パラメータは,個体数 20,世 代数 100,交叉確率 0.75,突然変異率は

Q-Flash Plus では、システムの電源が切れているとき(S5シャットダウン状態)に BIOS を更新する ことができます。最新の BIOS を USB

ダウンロードした書類は、 「MSP ゴシック、11ポイント」で記入で きるようになっています。字数制限がある書類は枠を広げず入力してく

Q7 

各テーマ領域ではすべての変数につきできるだけ連続変量に表現してある。そのため

借受人は、第 18