Upload
tomohiro-namba
View
91
Download
0
Embed Size (px)
Citation preview
C++ For Researchers 研究生のためのC++
企画・立案 難波知宏
C/C++基礎
第二回
プログラムはどのように
実行されるのか
第一節
コーディングから実行までの流れ
プリプロセス
コンパイル
リンク
プリプロセスを行う
(#includeなどを処理する)
機械語に変換し
中間ファイルを出力する
外部ライブラリと
結合する
コンパイルとリンク
• 一つのソースファイルから一つのオブジェクトファイルが生成される.
コンパイル
ソースファイルをオブジェクトファイルに変換すること
A.cpp A.obj
B.cpp B.obj
コンパイルとリンク
• 複数のオブジェクトファイルから一つの実行ファイルが生成される.
リンク
オブジェクトファイルを結合し,実行ファイルを生成すること
A.exe
A.cpp A.obj
B.cpp B.obj
question 2-01
#define X 137
int main()
{
int x = X;
}
ヒント
(0) 開発者用コマンドプロンプトを開く
スタートメニュー > Visual Studio 2015 > 開発者用コマンドプロンプト
(1) プリプロセス済みファイルの出力の仕方
cl /P “question 2-01.c”
(2) (アセンブラコードの出力の仕方)
cl /Fa “question 2-01.c”
(3) コンパイル済みファイルの出力の仕方
cl /c “question 2-01.c”
(4) リンク後ファイル(実行ファイル)の出力の仕方 link “question 2-01.obj”
演習 2-1 次のプログラムを、ヒントを参考にして、1.プリプロセス後, (2.アセンブ
ラコード), 3.コンパイル後, 4.リンク後の三段階それぞれの出力ファイルを作ってく
ださい。
question 2-02a
#include <stdio.h>
int add(int x, int y);
int main() {
printf("1 + 2 = %d.¥n", add(1, 2));
}
答え
(0) 開発者用コマンドプロンプトを開く
スタートメニュー > Visual Studio 2015 > 開発者用コマンドプロンプト
(1) オブジェクトファイルの出力の仕方
cl /c “question 2-02a.c” “question 2-02.c”
もしくは
cl /c “*.c”
(2) 実行ファイルの出力の仕方 link “question 2-02a.obj” “question 2-02b.obj”
もしくは
link “*.obj”
question 2-02b
int add(int x, int y)
{
return x + y;
}
演習 2-2 次のプログラムをコンパイルして実行ファイルを作ってください。
D:¥w070vg¥Desktop>link "question 2-02a.obj"
question 2-02a.obj : error LNK2019: 未解決の外部シンボル _add が関数 _main で参照されました。
question 2-02a.exe : fatal error LNK1120: 1 件の未解決の外部参照
リンクするとき、”question 2-02b.obj”をリンクし忘れたら…?
リンクエラー
「関数が定義されて
いない」 静的ライブラリか
オブジェクトファイルが
足りない
sample+ 2-01
#include <stdio.h>
int main()
{
int x;
__asm
{
mov eax, 32
mov ebx, 64
add ebx, eax
mov x, ebx
}
printf("x is %d.¥n", x);
}
コードの説明
mov eax, 32
レジスタeaxに32を格納する
mov ebx, 64
レジスタebxに64を格納する
add ebx, eax
レジスタebx, eaxを和をebxに書き込む
mov x, ebx
Cの変数xにレジスタebxの値を書き込む
おまけ インラインアセンブラ
C/C++のソースコードにはアセンブラコードを埋め込める
実行結果
x is 96.
C言語基礎
第二節
ヘッダファイルとソースファイル
ヘッダファイル
さまざまなソースファイルで必要となる部分を記述する
= 定義を書く
ソースファイル
関数の中身を記述する
= 実装を書く
ヘッダファイルとソースファイル
修正
再コンパイル
必要
再コンパイル
必要
修正された
ソースファイル
修正された
ソースファイル
変数とは
• すべての変数は、何らかの有効範囲(スコープ)と何らかの
生存期間を持つ.
変数
何らかのデータに名前付けしたもの
有効範囲 グローバル 全ソースコードからアクセス可
ファイル 単一ソースファイルからアクセス可
ブロック ソースコードの一部からアクセス可
生存期間 静的記憶 プログラム実行中常に存在
一時記憶 プログラム実行中一時的に存在
sample 2-01
// Global scope and static storage.
int a = 137;
// File scope and static storage.
static int b = 64;
void func() {
// Block scope and static storage.
static int c = 86;
// Block scope and template storage.
int d = 0;
}
int main() {
int x;
x = a; // OK.
x = b; // OK.
// x = c; // Compile error!
// x = d; // Compile error!
}
さまざまな変数
sample 2-02
// Lots and lots of variables...
int i, j, k, l, m, n;
double a, b, c;
void func1()
{
// ...
}
void func2()
{
// ...
}
int main()
{
func1();
func2();
}
悪い例 全部グローバル変数
にした
• どの変数がどこで使われているか
ぱっとみて分からない
分かりにくい
• 変数はプログラム終了まで
メモリ上に存在し続けることになる
メモリ効率が悪い
• 変数が増えてくると変数名が
重複する(衝突する)
大規模化できない
question 2-03
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
FILE* file;
int i;
char buffer[256];
int line_count = 0;
int main()
{
file = fopen("sample.txt", "r");
{
for (i = 0; !feof(file); i++)
{
fgets(buffer, sizeof(buffer) - 1, file);
printf(buffer);
++line_count;
}
}
fclose(file);
puts("");
printf("Number of lines is %d.¥n", line_count);
}
演習 2-3
次のプログラムを変数のスコープに気を付けてリファクタリングして下さい。
answer 2-03
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int line_count = 0;
FILE* file = fopen("sample.txt", "r");
{
char buffer[256];
for (int i = 0; !feof(file); i++)
{
fgets(buffer, sizeof(buffer) - 1, file);
printf(buffer);
++line_count;
}
}
fclose(file);
puts("");
printf("Number of lines is %d.¥n", line_count);
}
演習 2-3
答え
• スコープを横断して同じデータを受け渡す
=データの共有を行う
• ヒープ領域のデータを参照する
• (配列の走査を行う)
ポインタ
データが格納されているメモリのアドレスを値として持つ変数
実体 アドレス
&
*
ポインタ
sample 2-03
#include <stdio.h>
#include <stdlib.h>
void func(int* z)
{
printf("*z is %d.¥n", *z);
}
int main() {
int* x = (int*)malloc(sizeof(int));
int* y = x;
*y = 137;
printf("*x is %d.¥n", *x);
printf("*y is %d.¥n", *y);
func(x);
free(x);
}
実行結果
*x is 137.
*y is 137.
*z is 137.
ポインタの使用例
x, y, z はいずれも同じ
値を指している
スタックとヒープ
スタック
• 容量がオーバーすると、
スタックオーバーフローが発生
• ブロックを抜けると自動で解放
• 割り当て速度/メモリ効率ともに
高い
ヒープ
• 容量がオーバーすることはない
• プログラマが明示的に解放しなけれ
ばならない
• 割り当て速度、メモリ効率は低い
ブロックスコープの変数を
割り当てるメモリ領域
malloc()や new 演算子によって
割り当てられるメモリ領域
スタック or ヒープ ?
一般的に、以下の規則に従って決定
実行中
ずっと必要
static変数か
global変数
サイズが実行前に
分かっている
サイズが非常に
大きい
ヒープ
スタック
ヒープ
Yes
No
Yes
No
Yes
No
question 2-04
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int a[1000000];
int main()
{
printf("Input array size : ");
int size;
scanf("%d", size);
int b[size];
}
演習 2-4
次のプログラムはクラッシュします。クラッシュしないように修正してください。
answer 2-04
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* a = (int*)malloc(sizeof(int) * 1000000);
printf("Input array size : ");
int size;
scanf("%d", &size);
int* b = (int*)malloc(sizeof(int) *
size);
free(a);
free(b);
}
演習 2-4
解答例
関数
関数を適用することで,
• 頻繁に出てくる同じような処理をひとつにでき,
プログラムが明瞭になる.
• 実行ファイルのサイズが小さくなる.キャッシュヒット率が向上し,
実行速度が向上する(非常に小さい関数を除く).
関数
いくつかの処理をまとめて名前を付けたもの
sample 2-04
#include <stdio.h>
void func(int x)
{
printf("Address is %p.¥n", &x);
}
int main()
{
int x = 0;
printf("Address is %p.¥n", &x);
func(x);
}
実行結果
Address is 0022FF3C.
Address is 0022FF20.
関数の引数 = 値渡し
値渡しを確認するプログラム
同じ名前の変数でも
アドレスが異なる
(=実体が異なる)
sample 2-11
// Warning! This type is very large.
struct S { /* ... */ };
#if 0
// Argument is copied value.
void func(S s) { /* ... */ }
#else
// Argument is reference value.
void func(S* s) { /* ... */ }
#endif
ポインタの使いどころ
引数のコピーに時間
がかかり、低速
引数のコピーは
すぐ終わり、高速
構造体
構造体
複数のデータ型をひとまとめにしたデータ型
sample 2-04
struct S
{
int a;
float b;
double c;
};
int main()
{
S s;
s.a = 32;
s.b = 0.5f;
s.c = 3.14;
}
構造体の例
Sample 2-05 (構造体なし)
void doSomething( char* name, double* height, double* weight) { // ... } int main() { char* persons_name[8]; double persons_height[8]; double persons_weight[8]; persons_name[0] = "Jack"; persons_height[0] = 180; persons_weight[0] = 65; doSomething( persons_name[0], &persons_height[0], &persons_weight[0]); }
Sample 2-06 (構造体あり)
struct Person { char* name; double height; double weight; }; void doSomething(Person* person) { // ... } int main() { Person persons[8]; Person* person = &persons[0]; person->name = "Jack"; person->height = 180; person->weight = 65; doSomething(person); }
構造体がないと…
一つの変数で一つの
概念を表す
複数の変数で一つの
概念を表す
わかりにくい わかりやすい
C++基礎
第三節
参照
ポインタと同じく、値を共有する仕組み
参照
sample 2-07
int main()
{
int x = 137;
int& r = x;
printf("Address of x is %p.¥n", &x);
printf("Address of r is %p.¥n", &r);
}
実行結果
Address of x is 0018F900.
Address of r is 0018F900.
参照の例 (1)
sample 2-08
#include <cstdio>
// swap function using pointer.
void swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
// swap function using reference.
void swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
…
実行結果
x is 2, y is 1.
参照の例 (2)
→ポインタでできることの大半は、参照でもできる
じゃあ何であるんだよ
sample 2-08
#include <cstdio>
// swap function using pointer.
void swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
// swap function using reference.
void swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
…
参照の利点
①参照を使ったコードは見やすい
①ポインタ版
演算子多過ぎ
UZEEEEEEEE
②参照版
演算子不要
(引数のみ)
sample 2-09
struct S
{
int a;
float b;
double c;
};
void doSomething(S* s)
{
s->a = 32;
s->b = 0.4f;
s->c = 1.57;
}
void doSomething(S& s)
{
s.a = 32;
s.b = 0.4f;
s.c = 1.57;
}
①ポインタ版
->演算子
UZEEEEEEEE
②参照版
こっちのほうが
見やすいかも
sample 2-12
// Warning! This type is very large.
struct S { /* ... */ };
#if 0
// Argument is copied value.
void func(S s) { /* ... */ }
#else
// Argument is pointer.
void func(S* s) { /* ... */ }
// Argument is reference.
void func(S& s) { /* ... */ }
#endif
int main()
{
S s;
// Call by pointer.
func(&s);
// Call by reference.
func(s);
}
②参照渡しは、呼び出し元のコードを書き換える必要がない
参照の利点
①ポインタ版
呼び出し元は&をつけて
ポインタで渡さなくては
ならない ②参照版
呼び出し元はいちいち&
つけなくてよい
ポインタと参照の違い
③参照はポインタより制約が多く、その分安全
参照とポインタの違い
ポインタ
可
可
可
参照
不可
不可
不可
参照先の変更
NULLの参照
配列の走査
question 2-05
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct S
{
double x, y, z;
};
void print(S* s)
{
printf("%lf, %lf, %lf¥n", s->x, s->y, s->z);
}
int main()
{
S s;
S* p = &s;
p->x = 0.0;
p->y = 1.0;
p->z = 2.0;
print(p);
}
演習 2-5
次のポインタで書かれたプログラムを参照を使ったプログラムに変換して下さい。
answer 2-05
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct S
{
double x, y, z;
};
void print(S& s)
{
printf("%lf, %lf, %lf¥n", s.x, s.y, s.z);
}
int main()
{
S s;
S& p = s;
p.x = 0.0;
p.y = 1.0;
p.z = 2.0;
print(p);
}
演習 2-5
答え
new/delete 演算子
new delete
オブジェクトを動的に生成する newで作成したオブジェクトを解放する
sample 2-13
struct S { /* ... */ };
int main()
{
int* x = new int();
delete x;
S* s = new S();
delete s;
}
new[]/delete[] 演算子
new[] delete[]
newの配列版 deleteの配列版
sample 2-14
struct S { /* ... */ };
int main()
{
int* x = new int[5];
delete[] x;
S* s = new S[5];
delete[] s;
}
例外
• 従来は関数のリターンコード(戻り値)でエラーかどうかを調べていた
• エラーメッセージなどは、リターンコードとは別にエラー情報を取得
する専用の関数を呼び出して調べていた
例外
柔軟なエラー処理を実現する仕組み
めんどくさい
従来だと、
リターンコードがエラーコード
で使われるため、リターン
コードを自由に使えない!
ただし
例) DirectX9のCreateDevice 関数のリファレンス
例外は遅い
例外を使わないプログラムも多く存在する
中略