プログラミング論 II 2008年12月10日 変数のスコープ,記憶クラス. メモリ動的確保. http://www.ns.kogakuin.ac.jp/~ct13140/Prog.2009/ 概要 • 変数のスコープ – 重要.おそらく簡単. • 記憶クラス – 自動変数(auto)と静的変数(static). – スコープほどではないが重要. • 記憶の確保と解放 – 重要.やや難しい. – ポインタの理解が必要.是非使いこなして欲しい. G-2 2 変数のスコープ ブロック • C言語では,{ から } をブロックという. void main(){ for(…){ if(…){ } } if(…){ } } G-4 変数の宣言 • グローバル変数 – 関数の外(当然ブロックの外)で変数の宣言を行う. C, C++に依らず同じ. – 全ての関数内で使用可能. • ローカル変数 – C言語では, 変数の宣言は,ブロックの先頭でのみ行える. – C++では, 変数の宣言は,ブロック内の任意の位置で行える. – 宣言されたブロック内でのみ使用可能. G-5 変数のスコープ • 変数が使用できる範囲を"SCOPE"と言う. • SCOPEの広さは,変数を宣言した場所により異な る. • 変数宣言が含まれているブロックで最も狭いもの が選ばれる. – 変数は,宣言された中括弧{}の内側でのみ使用可 能.括弧の外では使えない. 括弧の内側には入れるが, 括弧の外側には出られない. G-6 ローカル変数とグローバル変数 #include <stdio.h> int a; void main(){ int b; } グローバル変数 ローカル変数 ローカル変数 void func(int c){ int d; ローカル変数 } G-7 ローカル変数 #include <stdio.h> int a; void main(){ int b; OK OK b = 7; printf("b = %d\n", b); } void func(int c){ int d; NG NG b = 8; printf("b = %d\n", b); } int bのスコープ範囲 G-8 ローカル変数 #include <stdio.h> int a; void main(){ int b; NG NG c = 7; printf("c = %d\n", c); } void func(int c){ int d; OK OK c = 8; printf("c = %d\n", c); } int cは, 「ブロックの外で 宣言されている」 ように見えるが, SCOPEの範囲は, 関数内です. ご注意あれ. int cのスコープ範囲 G-9 ローカル変数 #include <stdio.h> int a; void main(){ int b; NG NG d = 7; printf("d = %d\n", d); } void func(int c){ int d; OK OK d = 8; printf("d = %d\n", d); } int dのスコープ範囲 G-10 グローバル変数 #include <stdio.h> int a; void main(){ int b; OK OK a = 7; printf("a = %d\n", a); } void func(int c){ int d; OK OK a = 8; printf("a = %d\n", a); } グローバル変数 プログラム内の 全ての関数内で, 参照可能. int aのスコープ範囲 G-11 ローカル変数(ブロック内宣言) void main(){ int i; for(略){ int j; } } int iは内側(ピンク)ブロックに入れるが, int jはピンクブロックの外に出られない. G-12 ローカル変数(ブロック内宣言) void main(){ int i; if(略){ int j; } } G-13 ローカル変数(ブロック内宣言) void main(){ int i; { int j; } } 上記のように,for文やif文などなく 唐突に { } でブロックを作成しても問題ない. G-14 ローカル変数(ブロック内宣言) void main(){ int i; void main(){ int i, j; { int j; } } } 上記の違いは,int jのスコープの広さ. G-15 スコープの広さの善し悪し • 一般に,以下のように考えられている. – スコープは狭いほどよい. – グローバル変数は良くない • グローバル変数や広いスコープは,間違い(バグ )の原因になりやすい. – 変数の値がどこで変更さているのか把握しづらくなる – グローバル変数が多いと,各箇所で「理解しなくては ならない変数」の数が増えて辛くなる. G-16 同じ名前の変数が2個以上あったら • 同じ名前の変数が2個以上あったら,スコープの 狭い変数が勝つ – 感覚的には,近くで宣言されている変数が勝つ. • ただし,混乱の元なので,このようなことは行わな いことを強く推奨する. G-17 #include <stdio.h> int a = 3; void main(){ printf("a = %d\n", a); } 実行結果 a = 3 G-18 #include <stdio.h> void main(){ int a = 7; printf("a = %d\n", a); } 実行結果 a = 7 G-19 #include <stdio.h> int a = 3; void main(){ int a = 7; printf("a = %d\n", a); } 実行結果 a = 7 G-20 正しいプログラムだが, 「同じ名前の変数が 2個以上存在する」など, 混乱の元. このようなプログラムは 避けることを勧める. #include <stdio.h> int a = 3; void main(){ int a = 7; { int a = 9; printf("a = %d\n", a); } printf("a = %d\n", a); } 実行結果 a = 9 a = 7 G-21 同じ名前の変数が2個以上あったら • 以下の様に,同一ブロック内で同名の変数を2個 以上宣言するのはNG. void main(){ int x int x; が int y; 2個ある. NG! int x; x = 7; } どっちのint xなのか 分からない. 実行不可能! G-22 変数の記憶クラス 記憶クラス • auto, static, extern, register, typedef, volatile がある. – auto, static • 重要 – extern • 重要度高くない.分割コンパイル時に必要. – register • 重要度低い.高速化などに使用することあり. – typedef • 重要度はやや高い.「型に別名を付ける」のに使用. – volatile • 重要度低い.マルチスレッドプログラムなどで必要. G-24 auto変数,static変数 • auto変数は,ブロック内で誕生して,ブロックの 終わり(SCOPEの終わり)で消滅してしまう. – 変数が消滅すると,記憶されていた値も消える. auto変数は – 毎回,初期化される. – 通常のローカル変数がこれにあたる. 誕生と消滅を 繰り返す – スタック領域を使用. • auto int i; と,型の前に"auto"と書くと,auto変数となる. – ローカル変数は,明示的にstaticと書かない限り勝 手にauto変数となる. G-25 auto変数,static変数 • static変数は,プログラム実行開始時に作成さ れ,消滅しない. – 値はプログラム終了まで保持される. – 初期化は,プログラム開始時に1回だけ行われる. – 全てのグローバル変数がこれにあたる. • ただし,スライド36も参照のこと. – ヒープ領域を使用. • static int i; と,型の前に"static"と書くと,static変数と なる.(例え,ローカル変数であっても) – グローバル変数は,強制的にstatic変数となる. G-26 ヒープ領域とスタック領域 • ヒープ領域とスタック領域の大きな違い – ヒープ領域にはメモリが大量にあり,巨大なメ モリを使用したいときは,ヒープ領域上に確保 する. – スタック領域には少量のメモリしかない.大量 のメモリをスタック領域上に確保するのは不可 能. G-27 auto変数とstatic変数の例 #include <stdio.h> void main(){ i=0, j=6, j=7 int i; for(i=0; i<3; i++){ i=1, j=6, j=7 printf("i=%d, ", i); i=2, j=6, j=7 { int j =6; 実行結果 printf("j=%d, ", j); j++; printf("j=%d\n", j); } } int型変数jは, } ブロックに入る毎に作成され,初期化され, ブロックを抜けて消滅してしまう. G-28 auto変数とstatic変数の例 #include <stdio.h> void main(){ i=0, j=6, j=7 int i; for(i=0; i<3; i++){ i=1, j=7, j=8 printf("i=%d, ", i); i=2, j=8, j=9 { static int j =6; 実行結果 printf("j=%d, ", j); j++; printf("j=%d\n", j); } } int型変数jは, } プログラム開始時に1回だけ作成され,1回だけ初期化され, プログラム終了まで消滅せず,値は保持される. G-29 auto変数と static変数の例 例えstaticでも, jのスコープは, ブロックの中なので, ブロックの外からは アクセスできない. #include <stdio.h> void main(){ int i; for(i=0; i<3; i++){ printf("i=%d, ", i); 感覚的には, { 「存在しているが, static int j =6; printf("j=%d, ", j); 隠れていて見えない」 j++; printf("j=%d\n", j); } j = 100; /* j はここからは見えない! */ } } NG G-30 auto変数とstatic変数の例 #include <stdio.h> グローバル変数は, int x = 0; 強制的にstatic変数. 初期化は, void count(){ printf("x=%d\n", x); プログラムの開始時に1回. 値はプログラム終了まで x++; 保持される. } void main(){ x=0 count(); x=1 count(); x=2 count(); 実行結果 } G-31 auto変数とstatic変数の例 #include <stdio.h> void count(){ int x = 0; printf("x=%d\n", x); x++; } void main(){ count(); count(); count(); } 失敗作. 関数呼び出し毎に, x=0と初期化される. x=0 x=0 x=0 実行結果 G-32 auto変数とstatic変数の例 #include <stdio.h> static変数の初期化は, プログラム開始時に void count(){ 1回だけ. static int x = 0; 関数を抜けると, printf("x=%d\n", x); (見えなくなるが) 消滅せず, x++; 値は保持される. } void main(){ x=0 count(); x=1 count(); x=2 count(); 実行結果 } G-33 グローバル/ローカル auto/static • グローバル変数をautoにすることはできない. auto/staticを選べるのはローカル変数のみ. ローカル変数は(省略時は)自動的にauto変数 になる. • よって,「autoと積極的に記述すること」はほとん ど無い. • ローカル変数をstatic化するために,「static と積極的に記述すること」はある. G-34 グローバル/ローカル auto/static • auto/staticは,記憶クラスを指定するもので あり,スコープを指定するものではない. グローバル 変数 記憶クラス auto 記憶クラス static 存在しない 関数の外で変数宣言. 初期は最初の1回のみ. 値は消えない. 全ての関数からアクセス可能. ブロック内で変数宣言. ブロック内で変数宣言. ローカル 毎回初期化される. 初期は最初の1回のみ. ブロックを抜けて値が消える. ブロックを抜けても値が消えない. 変数 ブロック内からのみアクセス可能.ブロック内からのみアクセス可能. G-35 グローバル/ローカル auto/static • グローバル変数は,強制的に「初期化は最初に1 回のみ,値は消えない」となる. • しかし,static付きグローバル変数と, staticなしグローバル変数は意味が異なる. – static付きグローバル変数は,同一ファイル 内からのみアクセス可能. – staticなしグローバル変数は,別ファイルか らもアクセス可能. • 分割コンパイル時に,この違いが意味を持つ. G-36 typedef • typedefは,既存の型に別名を付けるのに使用 typedef int seisuu; /*以後「seisuu型」が使える. int型の別名に過ぎない */ int i; seisuu j; G-37 typedefの例 typedef unsigned int uint; – "unsigned int"に"uint"という別名が付いた. typedef long int int64, lint; – "long int"に,"int64","lint"という2個の別 名が付いた. typedef struct hoge sthoge; – "struct hoge"に,"sthoge"という別名が付い た. G-38 メモリの確保と解放 動的なメモリの確保 巨大なメモリの使用 メモリの確保と解放 • 「プログラムを開始後, 実行時にメモリを動的に確保し, 使い終わり不要になったらメモリを解放」 ということが可能. G-40 動的なメモリ確保の利点 • 必要な時のみメモリを占有し,不要になったら解放する 方が,メモリ使用効率が良い. – 占有しっぱなしはもったいない. • プログラムを開始するまで必要メモリ量が分からないこ とがある. – 動的確保を使わずにこの問題を回避するには, 「絶対に足りる」程度に巨大なメモリを確保しておく? • ローカル変数は,「スタック領域」という狭いメモリ領域を 使用するので,巨大なメモリの確保が不可能. 動的確保は「ヒープ領域」を使用.巨大メモリ使用可能. – この問題は,変数の記憶クラスをstaticすると解決するが,「 初期化動作の違い」には注意が必要. G-41 動的なメモリ確保の利点 • グローバル変数 int x[100]; は, プログラム実行中,ずっとメモリを占有し続ける. プログラム開始後,サイズを変更できない. • ローカル変数int x[100]; は, 変数寿命(スコープの範囲内)の間だけメモリを 占有するが,サイズを変更することはできない. スタック領域から確保するので,巨大なメモリを確 保できない. G-42 メモリの動的確保/解放 • 確保 – malloc(サイズ) を使用 • 解放 – free(アドレス) を使用 G-43 メモリの動的確保/解放の例(い) • 一般的な,動的メモリ確保のプログラム. void *p; /* ↓1000000バイトのメモリを要求 */ p = malloc(1000000); /* 1000000バイトのメモリが使用可能になった. メモリを使用する.*/ free(p); /* 使い終わったので解放 */ G-44 malloc • void *mallo(size_t size); – メモリ確保を要求する関数. – 引数に,確保を要求するメモリのサイズを指定する. 単位はバイト. – 確保に成功したら,確保されたメモリの先頭アドレスが戻り値と して返される. – 確保に失敗したら,NULLが返される. • 計算機の使用可能メモリ(残りメモリ)よりも大きなメモリを 要求したら,確保に失敗する. G-45 free • free(void *p) – メモリを解放する関数. – 引数には,解放したいメモリ領域の先頭アドレスを指 定する. (mallocにより与えられたアドレスを渡せばOK) G-46 メモリの動的確保/解放の例(ろ) • 一般的な,動的メモリ確保のプログラム. #include <stdio.h> void main(){ void *p; char *p_ch; p = malloc(1000000); /* 1000000バイトのメモリを要求 */ if( p == NULL ){ fprintf(stderr,"メモリ確保に失敗しました.\n"); exit(1); } p_ch = (char *)p; /* 1000000バイトのメモリが使用可能になった.メモリを使用する.*/ free(p); /* 使い終わったので解放 */ } G-47 動的確保メモリの使用例 void *p; int *p_i; int i; 「int型は何バイトか?」 は,sizeof(int)で 知ることができる. p = malloc(10000*sizeof(int)); if( p == NULL ){ fprintf(stderr,"メモリ確保に失敗しました.\n"); exit(1); } 戻り値は「アドレス」です. mallocはvoid *を返すので, p_i = (int *)p; int *に変換. for(i=0; i<10000; i++){ *(p_i + i) = i*2; } for(i=0; i<10000; i++){ int x = *(p_i + i); printf("%d : %d\n", i, x); } free(p); /* 代入してみる */ /* 値を読み出してみる */ /* 使い終わったので解放 */ G-48 おまけ:巨大なメモリの使用 • 以下のプログラムをコンパイル&実行したら,実 行時にエラーが発生し,強制終了された. #include <stdio.h> void main(){ int data[1000000]; printf("Hello, World!\n"); } G-49 おまけ:巨大なメモリの使用 • 以下のプログラムをコンパイル&実行したら,正 しく実行された. #include <stdio.h> int data[1000000]; void main(){ printf("Hello, World!\n"); } G-50 おまけ:巨大なメモリの使用 • 以下のプログラムをコンパイル&実行したら,正 しく実行された. #include <stdio.h> void main(){ static int data[1000000]; printf("Hello, World!\n"); } G-51 練習 0 void main(){ int src[100], dst[100], i; src[0]=…; src[1]=…; src[2]=…; ?? } src[]の100個をdst[]にコピーするには? つまり,src[0]の値をdst[0]にコピー,src[1] の値をdst[1]にコピー,src[2]の値をdst[2] にコピー,src[99]の値をdst[99]にコピー. G-52 解答 0 void main(){ int src[100], dst[100], i; for(i=0; i<100; i++){ dst[i] = src[i]; } } G-53
© Copyright 2024 ExpyDoc