45
C++ For Researchers 研究生のためのC++ 企画・立案 難波知宏

研究生のためのC++ no.2

Embed Size (px)

Citation preview

Page 1: 研究生のためのC++ no.2

C++ For Researchers 研究生のためのC++

企画・立案 難波知宏

Page 2: 研究生のためのC++ no.2

C/C++基礎

第二回

Page 3: 研究生のためのC++ no.2

プログラムはどのように

実行されるのか

第一節

Page 4: 研究生のためのC++ no.2

コーディングから実行までの流れ

プリプロセス

コンパイル

リンク

プリプロセスを行う

(#includeなどを処理する)

機械語に変換し

中間ファイルを出力する

外部ライブラリと

結合する

Page 5: 研究生のためのC++ no.2

コンパイルとリンク

• 一つのソースファイルから一つのオブジェクトファイルが生成される.

コンパイル

ソースファイルをオブジェクトファイルに変換すること

A.cpp A.obj

B.cpp B.obj

Page 6: 研究生のためのC++ no.2

コンパイルとリンク

• 複数のオブジェクトファイルから一つの実行ファイルが生成される.

リンク

オブジェクトファイルを結合し,実行ファイルを生成すること

A.exe

A.cpp A.obj

B.cpp B.obj

Page 7: 研究生のためのC++ no.2

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.リンク後の三段階それぞれの出力ファイルを作ってく

ださい。

Page 8: 研究生のためのC++ no.2

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 次のプログラムをコンパイルして実行ファイルを作ってください。

Page 9: 研究生のためのC++ no.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”をリンクし忘れたら…?

リンクエラー

「関数が定義されて

いない」 静的ライブラリか

オブジェクトファイルが

足りない

Page 10: 研究生のためのC++ no.2

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.

Page 11: 研究生のためのC++ no.2

C言語基礎

第二節

Page 12: 研究生のためのC++ no.2

ヘッダファイルとソースファイル

ヘッダファイル

さまざまなソースファイルで必要となる部分を記述する

= 定義を書く

ソースファイル

関数の中身を記述する

= 実装を書く

Page 13: 研究生のためのC++ no.2

ヘッダファイルとソースファイル

修正

再コンパイル

必要

再コンパイル

必要

修正された

ソースファイル

修正された

ソースファイル

Page 14: 研究生のためのC++ no.2

変数とは

• すべての変数は、何らかの有効範囲(スコープ)と何らかの

生存期間を持つ.

変数

何らかのデータに名前付けしたもの

有効範囲 グローバル 全ソースコードからアクセス可

ファイル 単一ソースファイルからアクセス可

ブロック ソースコードの一部からアクセス可

生存期間 静的記憶 プログラム実行中常に存在

一時記憶 プログラム実行中一時的に存在

Page 15: 研究生のためのC++ no.2

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!

}

さまざまな変数

Page 16: 研究生のためのC++ no.2

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();

}

悪い例 全部グローバル変数

にした

• どの変数がどこで使われているか

ぱっとみて分からない

分かりにくい

• 変数はプログラム終了まで

メモリ上に存在し続けることになる

メモリ効率が悪い

• 変数が増えてくると変数名が

重複する(衝突する)

大規模化できない

Page 17: 研究生のためのC++ no.2

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

次のプログラムを変数のスコープに気を付けてリファクタリングして下さい。

Page 18: 研究生のためのC++ no.2

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

答え

Page 19: 研究生のためのC++ no.2

• スコープを横断して同じデータを受け渡す

=データの共有を行う

• ヒープ領域のデータを参照する

• (配列の走査を行う)

ポインタ

データが格納されているメモリのアドレスを値として持つ変数

実体 アドレス

&

*

ポインタ

Page 20: 研究生のためのC++ no.2

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 はいずれも同じ

値を指している

Page 21: 研究生のためのC++ no.2

スタックとヒープ

スタック

• 容量がオーバーすると、

スタックオーバーフローが発生

• ブロックを抜けると自動で解放

• 割り当て速度/メモリ効率ともに

高い

ヒープ

• 容量がオーバーすることはない

• プログラマが明示的に解放しなけれ

ばならない

• 割り当て速度、メモリ効率は低い

ブロックスコープの変数を

割り当てるメモリ領域

malloc()や new 演算子によって

割り当てられるメモリ領域

Page 22: 研究生のためのC++ no.2

スタック or ヒープ ?

一般的に、以下の規則に従って決定

実行中

ずっと必要

static変数か

global変数

サイズが実行前に

分かっている

サイズが非常に

大きい

ヒープ

スタック

ヒープ

Yes

No

Yes

No

Yes

No

Page 23: 研究生のためのC++ no.2

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

次のプログラムはクラッシュします。クラッシュしないように修正してください。

Page 24: 研究生のためのC++ no.2

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

解答例

Page 25: 研究生のためのC++ no.2

関数

関数を適用することで,

• 頻繁に出てくる同じような処理をひとつにでき,

プログラムが明瞭になる.

• 実行ファイルのサイズが小さくなる.キャッシュヒット率が向上し,

実行速度が向上する(非常に小さい関数を除く).

関数

いくつかの処理をまとめて名前を付けたもの

Page 26: 研究生のためのC++ no.2

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.

関数の引数 = 値渡し

値渡しを確認するプログラム

同じ名前の変数でも

アドレスが異なる

(=実体が異なる)

Page 27: 研究生のためのC++ no.2

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

ポインタの使いどころ

引数のコピーに時間

がかかり、低速

引数のコピーは

すぐ終わり、高速

Page 28: 研究生のためのC++ no.2

構造体

構造体

複数のデータ型をひとまとめにしたデータ型

Page 29: 研究生のためのC++ no.2

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;

}

構造体の例

Page 30: 研究生のためのC++ no.2

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); }

構造体がないと…

一つの変数で一つの

概念を表す

複数の変数で一つの

概念を表す

わかりにくい わかりやすい

Page 31: 研究生のためのC++ no.2

C++基礎

第三節

Page 32: 研究生のためのC++ no.2

参照

ポインタと同じく、値を共有する仕組み

参照

Page 33: 研究生のためのC++ no.2

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)

Page 34: 研究生のためのC++ no.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;

}

実行結果

x is 2, y is 1.

参照の例 (2)

→ポインタでできることの大半は、参照でもできる

Page 35: 研究生のためのC++ no.2

じゃあ何であるんだよ

Page 36: 研究生のためのC++ no.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

②参照版

こっちのほうが

見やすいかも

Page 37: 研究生のためのC++ no.2

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);

}

②参照渡しは、呼び出し元のコードを書き換える必要がない

参照の利点

①ポインタ版

呼び出し元は&をつけて

ポインタで渡さなくては

ならない ②参照版

呼び出し元はいちいち&

つけなくてよい

Page 38: 研究生のためのC++ no.2

ポインタと参照の違い

③参照はポインタより制約が多く、その分安全

参照とポインタの違い

ポインタ

参照

不可

不可

不可

参照先の変更

NULLの参照

配列の走査

Page 39: 研究生のためのC++ no.2

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

次のポインタで書かれたプログラムを参照を使ったプログラムに変換して下さい。

Page 40: 研究生のためのC++ no.2

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

答え

Page 41: 研究生のためのC++ no.2

new/delete 演算子

new delete

オブジェクトを動的に生成する newで作成したオブジェクトを解放する

sample 2-13

struct S { /* ... */ };

int main()

{

int* x = new int();

delete x;

S* s = new S();

delete s;

}

Page 42: 研究生のためのC++ no.2

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;

}

Page 43: 研究生のためのC++ no.2

例外

• 従来は関数のリターンコード(戻り値)でエラーかどうかを調べていた

• エラーメッセージなどは、リターンコードとは別にエラー情報を取得

する専用の関数を呼び出して調べていた

例外

柔軟なエラー処理を実現する仕組み

めんどくさい

従来だと、

リターンコードがエラーコード

で使われるため、リターン

コードを自由に使えない!

Page 44: 研究生のためのC++ no.2

ただし

Page 45: 研究生のためのC++ no.2

例) DirectX9のCreateDevice 関数のリファレンス

例外は遅い

例外を使わないプログラムも多く存在する

中略