変数のスコープ,記憶クラス,メモリ動的確保

download report

Transcript 変数のスコープ,記憶クラス,メモリ動的確保

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