C言語入門

1
C言語入門
第9週
プログラミング言語Ⅰ(実習を含む。),
計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
2
関数の設計
3
教科書 pp.190-197.
例題: 1~nの和
• 1~nの和
𝑛
𝑖 = 1 + 2 + ⋯+ 𝑛
𝑖=1
を求めるプログラムを考える。
• 知っているやり方
• ループを使ってi=1~nまで増やしながら足す
• 和の公式を計算する
𝑛
𝑖=1
𝑛 𝑛+1
𝑖=
2
4
教科書 pp.190-197.
例題: 1~nの和
• まず、何も考えずにループで書いてみる
sum_ex1_for.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void main()
{
int i, n;
int sum = 0;
fprintf(stderr, "n = ?\b");
scanf("%d", &n);
for (i = 1; i <= n; i++) {
sum += i;
}
printf("%d\n", sum);
}
main 関数の中に
すべての処理を入れた状態
mintty + bash + GNU C
$ gcc sum_ex1_for.c && ./a <<<10
n = 55
5
教科書 pp.190-197.
例題: 1~nの和
• main 関数と sum 関数に分割してみる
sum_ex2_for.c
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int sum(int n)
{
int i;
int sum = 0;
for (i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
sum_ex2_for.c
14
15
16
17
18
19
20
21
22
void main()
{
int n;
fprintf(stderr, "n = ?\b");
scanf("%d", &n);
printf("%d\n", sum(n));
}
ファイルはまだ、1ファイルのまま
分けてない状態
mintty + bash + GNU C
$ gcc sum_ex2_for.c && ./a <<<10
n = 55
6
教科書 pp.190-197.
例題: 1~nの和
• main と sum を 2 ファイルに分割してみる
sum_ex3_for.c
1
2
3
4
5
6
7
8
9
10
sum_ex3_main.c
int sum(int n)
{
int i;
int sum = 0;
for (i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int sum(int n);
void main()
{
int n;
fprintf(stderr, "n = ?\b");
scanf("%d", &n);
printf("%d\n", sum(n));
}
mintty + bash + GNU C
$ gcc sum_ex3_main.c sum_ex3_for.c && ./a <<<10
n = 55
7
教科書 pp.190-197.
訂正2015-06-26
追加: 2行目: #define SUM_EX4_H
例題: 1~nの和
• ヘッダファイルも作ってみる
sum_ex4_for.c
1
2
3
4
5
6
7
8
9
10
11
12
sum_ex4_main.c
include "sum_ex4.h"
int sum(int n)
{
int i;
int sum = 0;
for (i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "sum_ex4.h"
void main()
{
int n;
fprintf(stderr, "n = ?\b");
scanf("%d", &n);
printf("%d\n", sum(n));
}
sum_ex4.h
1
2
3
4
#ifndef SUM_EX4_H
#define SUM_EX4_H
int sum(int n);
#endif//SUM_EX4_H
mintty + bash + GNU C
$ gcc sum_ex4_main.c sum_ex4_for.c && ./a <<<10
n = 55
8
教科書 pp.190-197.
訂正2015-06-26
追加: 2行目: #define SUM_EX4_H
例題: 1~nの和
• 別の方法で関数を実装し直してみる
sum_ex4_formula.c
1
2
3
4
5
6
sum_ex4_main.c
#include "sum_ex4.h"
int sum(int n)
{
return n * (n + 1) / 2;
}
sum 関数だけ差し替え
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include "sum_ex4.h"
void main()
{
int n;
fprintf(stderr, "n = ?\b");
scanf("%d", &n);
printf("%d\n", sum(n));
}
sum_ex4.h
1
2
3
4
#ifndef SUM_EX4_H
#define SUM_EX4_H
int sum(int n);
#endif//SUM_EX4_H
mintty + bash + GNU C
$ gcc sum_ex4_main.c sum_ex4_formula.c && ./a <<<10
n = 55
9
自分自身を呼び出す関数
再帰(RECURSION)
10
教科書 pp.190-197.
例題: 再帰による1~nの和
• 1~nの和
𝑛
𝑖 = 1 + 2 + ⋯+ 𝑛
𝑖=1
を求めるプログラムを考える。
• 知っているやり方
• ループを使ってi=1~nまで増やしながら足す
• 和の公式を計算する
𝑛
𝑛 𝑛+1
𝑖=
2
𝑖=1
• 他にもやり方がある?
11
教科書 pp.190-197.
例題: 再帰による1~nの和
• 1~nの和は以下のように1~n-1の和とnに分解出来る
𝑛
𝑖 = 1 + 2 + ⋯+ 𝑛 − 1 + 𝑛
𝑛
つまり
𝑖=
𝑖=1
𝑖=1
𝑛−1
𝑖+𝑛: 1<𝑛
𝑖=1
1 : (𝑛 = 1)
これを関数にしてみると・・・
12
教科書 pp.190-197.
例題: 再帰による1~nの和
訂正2015-06-26
追加: 2行目: #define SUM_EX4_H
• 1~n-1とnに分けて計算してみる
sum_ex4_recursion.c
1
2
3
4
5
6
7
8
9
sum_ex4_main.c
#include "sum_ex4.h"
int sum(int n)
{
if (n == 1) {
return 1;
}
return sum(n - 1) + n;
}
sum 関数だけ差し替え
1 #include <stdio.h>
#include "sum_ex4.h"
n-1 2にして
3
自分自身を呼んでいる
4 void main()
5 {
6
int n;
再帰的処理
7
8
fprintf(stderr, "n = ?\b");
9
scanf("%d", &n);
10
11
printf("%d\n", sum(n));
12 }
sum_ex4.h
1
2
3
4
#ifndef SUM_EX4_H
#define SUM_EX4_H
int sum(int n);
#endif//SUM_EX4_H
mintty + bash + GNU C
$ gcc sum_ex4_main.c sum_ex4_recursion.c && ./a <<<10
n = 55
13
関数の演習
階乗(FACTORIAL)の計算
14
教科書 pp.190-197.
階乗 (factorial)関数
ループの場合はこの数列を作り𝑛個積算していく
• 考え方
• ループの場合
𝑛! = 1 ⋅ 1 ⋅ 2 ⋯ 𝑛 − 1 ⋅ 𝑛
𝑛
• 再帰の場合
𝑛! = 𝑛 ⋅ 𝑛 − 1 !
𝑛−1 != 𝑛−1 ⋅ 𝑛−2 !
⋮
1! = 1 ⋅ 0!
0! = 1
再帰の場合は
1つ小さな値を
引数にして
自分で自身を呼ぶ
だけ
それ以上小さく
出来なくなったら
ルールに沿って
適当な値を返す
15
教科書 pp.190-197.
演習: factorial_practice1_for.c
• int型の整数𝑛について階乗(factorial) 𝑛!を
求める関数factorial(n)をforループを用い
てfactorial_practice1_for.cに実装せよ
• main関数とヘッダは、
factorial_practice1_main.c 及び
factorial_practice1.h に用意してある。
• 引数
• int n : 整数
• 戻り値
• 𝑛! をint型で返せ
訂正2015-06-26
誤: factoriali
正: factorial
16
教科書 pp.190-197.
演習: factorial_practice1_recursion.c
• int型の整数𝑛について階乗(factorial) 𝑛!を
求める関数factorial(n)を再帰を用いて
factorial_practice1_recursion.c に実装せよ
• main関数とヘッダは、
factorial_practice1_main.c 及び
factorial_practice1.h に用意してある。
• 引数
• int n : 整数
• 戻り値
• 𝑛! をint型で返せ
訂正2015-06-26
誤: factoriali
正: factorial
17
例題: factorial_practice1_*.c
• 実行例
mintty + bash + GNU C
$ gcc factorial_practice1_main.c factorial_practice1_for.c && ./a <<<5
n = 120
mintty + bash + GNU C
$ gcc factorial_practice1_main.c factorial_practice1_recursion.c && ./a <<<5
n = 120
訂正2015-06-26
誤: factoriali
正: factorial
18
通用範囲、格納場所、初期化の違い
記憶クラス指定子
19
変数の初期化
• 明示的な初期化がない場合
• 外的変数、静的変数→0
• 自動変数、レジスタ変数→不定
• 初期化する場合
• 外的変数、静的変数←定数式でのみ初期化可
• コンパイル時に1度だけ初期化される
• 自動変数、レジスタ変数←任意の式で初期化可
• 実行時にブロック毎に初期化される
[1] pp.103-104., p.273.
20
記憶クラス指定子による
通用範囲と格納場所と初期化
関数外
無指定
広域的
メインメモリー
定数でコンパイル時1回
関数内
局所的
スタック
変数可、実行時毎回
備考
標準的な
外部変数と内部変数
auto
局所的
スタック
変数可、実行時毎回
register
局所的
レジスタまたはスタック
変数可、実行時毎回
アドレス演算子使用不可
extern
広域的
メインメモリー
宣言のみ可能
広域的
メインメモリー
宣言のみ可能
初期化不可
(定義時に初期化済み)
static
広域的(ファイル内限定)
メインメモリー
定数でコンパイル時1回
局所的
メインメモリー
定数でコンパイル時1回
静的変数
21
記憶クラス指定子:無指定
関数外
• 外部変数の定義
• 広域的
関数内
• 自動変数の定義
• 局所的
• コンパイル時に1回だけ初
期化
• 定数でのみ初期化可能
• 各ブロックの実行時に毎回
初期化
• 変数でも初期化可能
• auto と同義
22
記憶クラス指定子: auto
関数外
• 使用不可
関数内
• 自動変数の定義
• 局所的
• 各ブロックの実行時に毎回
初期化
• 変数でも初期化可能
23
記憶クラス指定子: register
関数外
• 使用不可
関数内
• レジスタ変数の定義
• 局所的
• 各ブロックの実行時に毎回
初期化
• 変数でも初期化可能
• アドレス演算子&使用不可
レジスタ変数は自動変数の一種
優先的にレジスタに割り当てるが
レジスタは数が少ないので
必ずしもレジスタに割り当てられるわけではない
24
記憶クラス指定子: static
関数外
• 静的変数の定義
• 広域的(ただしファイル外か
らはアクセス出来ない)
• コンパイル時に1回だけ初
期化
• 定数でのみ初期化可能
関数内
• 静的変数の定義
• 局所的
• コンパイル時に1回だけ初
期化
• 定数でのみ初期化可能
25
記憶クラス指定子: extern
関数外
• 外部変数の宣言
• 広域的
関数内
• 外部変数の宣言
• 広域的
• 定義の側で初期化を行う
(宣言の側では初期化出来
ない)
• 定義の側で初期化を行う
(宣言の側では初期化出来
ない)
26
教科書pp.177-179.
変数のメモリへの割り当て
• 通常の自動変数はスタックに作成される
#include <stdio.h>
void main()
{
int a;
int b;
int c;
}
int c
int b
実行時に随時スタックに積んで行く
使い終わったらスタックから破棄する
int a
呼び出し元アドレス
27
教科書pp.177-179.
変数のメモリへの割り当て
• 外部変数、静的変数はメモリの特定領域へ
作成される
#include <stdio.h>
int a;
void main()
{
static int b;
int c;
}
:
int b
コンパイル時にあらかじめ
決められたメモリに配置される
int a
int c
:
呼び出し元アドレス
28
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
•
•
•
•
LIFO(Last-In-First-Out)
FILO(First-In-Last-Out)
先入れ後出し
後入れ先出し
• push (一番上に格納)
• pop (一番上から取り出し)
• サブルーチン呼び出し
等で呼び出し元アドレ
スの保存やローカル変
数の作成に使われる
呼び出し元アドレス
29
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
•
•
•
•
LIFO(Last-In-First-Out)
FILO(First-In-Last-Out)
先入れ後出し
後入れ先出し
• push (一番上に格納)
• pop (一番上から取り出し)
• サブルーチン呼び出し
等で呼び出し元アドレ
スの保存やローカル変
数の作成に使われる
int a
呼び出し元アドレス
30
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
•
•
•
•
LIFO(Last-In-First-Out)
FILO(First-In-Last-Out)
先入れ後出し
後入れ先出し
• サブルーチン呼び出し
等で呼び出し元アドレ
スの保存やローカル変
数の作成に使われる
• push (一番上に格納)
• pop (一番上から取り出し)
int b
int a
呼び出し元アドレス
31
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
•
•
•
•
LIFO(Last-In-First-Out)
FILO(First-In-Last-Out)
先入れ後出し
後入れ先出し
• push (一番上に格納)
• pop (一番上から取り出し)
• サブルーチン呼び出し
等で呼び出し元アドレ
スの保存やローカル変
数の作成に使われる
int a
呼び出し元アドレス
32
教科書pp.177-179.
スタックメモリ
• 積み木式データ構造
•
•
•
•
LIFO(Last-In-First-Out)
FILO(First-In-Last-Out)
先入れ後出し
後入れ先出し
• push (一番上に格納)
• pop (一番上から取り出し)
• サブルーチン呼び出し
等で呼び出し元アドレ
スの保存やローカル変
数の作成に使われる
呼び出し元アドレス
33
レジスタ
• CPU 内で計算に用いられるメモリの一種
• メインメモリよりも高速だが数が少ない
• x86では32bitの汎用レジスタがたったの8つ
(eax, ecx, edx, ebx, esp, ebp, esi, edi)しかない
計算する際は必要な値を
レジスタにロードしては戻している
レジスタ
可能なら限り値をレジスタに
置きっ放しにしておくと速くなる
メインメモリ外(CPU内)にあるためアドレスがない
メインメモリー
34
[1] p.98.
変数の宣言と定義の違い
• 宣言
• 変数の型やサイズを指示する
• メモリへの割り当ては行わない
• 定義
• 変数の宣言を行う
• 実際にメモリへの割り当ても行う
分割コンパイルする場合、複数のファイルで同じ変数を重複して定義は出来ない
定義はどれか1つのファイルで1回だけ行い
あとのファイルは宣言だけして定義済みの変数を参照する。
35
[1] p.98.
変数の宣言と定義
• 複数のファイルでの外部変数を共有する
extern_ex1_main.c
extern_ex1_sub.c
#include <stdio.h>
extern int sum; // 外部変数の宣言
void sub(int); // 外部関数の宣言
int sum = 0; // 外部変数の定義と初期化
// 外部関数の定義
void sub(int x)
{
sum += x;
}
void main()
{
int i;
for (i = 0; i < 10; i++) {
sub(i);
}
printf("%d\n", sum);
}
36
教科書p.164., [1] p.101.
static 宣言
• static 宣言には 3 種類の意味がある
外部的
内部的
変数 外部変数に対する static 宣言 内部変数に対する static 宣言
関数 関数に対する static 宣言
外部変数や関数を
宣言されたファイル外から隠す
内部変数を
静的に割り当てる
37
教科書p.164., [1] p.101.
外部変数に対する static 宣言
• 複数ファイルでの外部変数共用
static_ex1_main.c
static_ex1_sub.c
#include <stdio.h>
int val1 = 1; // 外部変数の定義と初期化
static int val2 = 2; // 外部変数の定義と初期化
extern int val1; // 外部変数の宣言
//extern int val2; // 外部変数の宣言
void main()
{
printf("val1: %d\n", val1);
//printf("val2: %d\n", val2);
// val2 は static 変数なので
// ファイル外からアクセス出来ない
}
38
教科書p.164., [1] p.101.
内部変数に対する static 宣言
• 複数ファイルでの外部変数共用
static_ex2_main.c
static_ex2_sub.c
void sub1(); // 外部関数の宣言
void sub2(); // 外部関数の宣言
#include <stdio.h>
void main()
{
sub1();
sub1();
sub2();
sub2();
}
void sub1()
{
int i = 0; // 自動変数の定義
printf("sub1: %d\n", i++);
}
void sub2()
{
static int i = 0; // 静的変数の定義
printf("sub2: %d\n", i++);
}
mintty + bash + GNU C
$ gcc
sub1:
sub2:
sub1:
sub2:
static_ex1_main.c static_ex1_sub.c && ./a
0
0
0
1
静的変数はコンパイル時に
1回だけしか初期化されない
39
教科書p.164., [1] p.101.
関数に対する static 宣言
• 複数ファイルでの外部変数共用
static_ex3_main.c
static_ex3_sub.c
#include <stdio.h>
#include <stdio.h>
void sub1(); // 外部関数の宣言
//void sub2(); // 外部関数の宣言
void sub1()
{
printf("sub1\n");
}
void main()
{
sub1();
sub1();
// sub2(); // sub2 は static 関数なので
// sub2(); // ファイル外からは呼べない
}
static void sub2()
{
printf("sub2\n");
}
40
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久
訳、プログラミング言語C 第2版 ANSI 規格準
拠、共立出版(1989)