手続き
命令型言語では、複雑なシステムを部品化(モジュール化)し、その部品化されたプログラム単位を手続き
と呼ぶ。(アセンブリ言語でいうサブルーチン
)
手続き定義のEBNF
<手続き定義> ::= <返り値の型名><手続き名>(<仮引数>)<手続き本体>
int square(int x) { // xが仮引数
return x * x;
}
値渡しとポインタ渡し(値共有)と参照渡し(変数共有)
変数は、大きく分類して「値型」「ポインタ型・参照型」がある。
- 値型変数 : 変数は値(データ)そのものを保持する
- ポインタ型・参照型 : 値(データやオブジェクト)のアドレス値・参照値を保持して、値をポイント・参照する
変数の渡し方には「値渡し」「ポインタ渡し」「参照渡し」がある。
・値渡しの特徴
変数が保持している値をコピーして渡す。値型変数であれば値のコピー、アドレス型・参照型変数であればアドレス値・参照値のコピーを渡す。
実引数と仮引数は別々の変数。仮引数に再代入しても実引数に影響しない。
→関数に引数のコピー
を渡す方法であり、関数内で引数の変更をしても、呼び出し元のデータに影響を与えたくない場合に使用する。
def increment(value):
value = value + 1
x = 15
increment(x)
print(x) # 15(変更されていない)
a = increment(x)
print(a) # 16
・ポインタ渡しの特徴
ポインタ型・参照型変数が保持しているアドレス値・参照値を渡す。変数が保持している値(アドレス値・参照値)を渡すので値渡しの一種である。「参照の値渡し」である。
実引数と仮引数は別々の変数。仮引数に再代入しても実引数に影響しない。
ポイント・参照先の値(データやオブジェクト)はコピーせずに共有する。
ポイント・参照先の値の内容を変更すると呼び出し元の値に影響する。
→関数内でポイント・参照先の値を変更し、その変更が呼び出し元のデータに反映されることを望む場合に使用する。また大きなデータを渡す場合にも必要となる。
C言語の配列は自動的にポインタ渡しとなる。PHPの配列は値渡しで配列のコピーが渡る。
C言語の構造体は値型で構造体のコピーが渡る。
def increment_array(arr, index):
arr[index] = arr[index] + 1
numbers = [1, 2, 3]
increment_array(numbers, 1)
print(numbers) # 出力: [1, 3, 3](更新されている)
・参照渡しの特徴
変数への参照を渡す。
実引数と仮引数は同一の変数。仮引数に再代入すると実引数に代入される。
→値型変数の値のコピーせずに共有したい場合に使用する。戻り値が複数ある場合に引数に値を返したいときに使用する。
値渡しは値(データ)のコピーを渡し、ポインタ渡しや参照渡しは値(データやオブジェクト)を共有し、参照渡しは変数を共有する。
void increment(int a, int *b) { // アドレスを仮引数bで受ける
a = 10;
*b = 10; // ポインタbの参照先を10に変更
}
int main() {
int x = 5;
int y = 5;
increment(x, &y); // アドレス自体(&y)を値渡しする
printf("%d %d\n", x, y) ; // 5 10
}
ここで一つ思い出したいのが、scanf("%d", &x);
である。ユーザーからの入力を受け取る際に書いていたものだが、これはアドレス渡しに依るものである。ここで意味していることは、x
のアドレスをscanf()
の仮引数に渡し、x
の右辺値をポインタ経由で書き換えることである。
スコープと存続範囲
変数の存続範囲とは、変数に対してメモリ領域を割り当てている期間のこと。
スコープ
=存続範囲
ではない。以下に、その例を与える。
void main() {
int *p;
{
int x = 6;
p = &x;
} // xのスコープはここまでだが存続範囲は続く
//printf("%d\n", x); // スコープ外なのでエラー
printf("%d\n", *p); // 6
*p = 15; // 値の書き換えも可能
printf("%d\n", *p); // 15
}
ここでx
のスコープは複文({}で囲まれた文)の期間に等しいが、x
のメモリ領域はprintf("%d\n", *p); // 6
より、まだ存続していることがわかる。したがって、スコープ
=存続範囲
ではないといえる。
メモリ
実行時スタック
スタックの振る舞いの例として以下に図を掲載する。スタックにはpush
でレコード(関数の呼び出しごとに必要な情報を格納するメモリ領域)を収納し、pop
で上から順にレコードを取り出し、実行する。
メモリ領域の構成
以下に、C言語におけるメモリ領域を図で示す。
ヒープは動的にメモリを確保するための領域であり、malloc()
やfree()
を通じて自由に操作可能である。ここでmalloc()
について注意点を述べる。
mallocによる動的メモリ確保
例えば、array[8]
のint型をメモリで確保するとき、(4バイト)*8
の32
バイトが必要となる。しかし、malloc(8)
では8
バイトの領域しか確保されていない。したがって、malloc(sizeof(int) * 8)
とするのである。
sizeof(int)
は4
バイトを示す。