1. ハードウェアとプログラム
1.1. コンピュータの構成要素
まずは、コンピュータを構成する5大要素(装置)について説明します。
- 演算
- データに対する演算を行う
CPU・GPUなど - 制御
- 各装置を制御する
基本的にはCPUが行う - 記憶
- プログラムやデータを記憶する
メモリ・HDD・SSDなど - 入力
- コンピュータに外部からデータを入力する
キーボード・マウス・マイクなど - 出力
- コンピュータから外部にデータを出力する
ディスプレイ・プリンタなど
##1.2. ハードウェアとプログラム
プログラムが実行される際にハードウェア上ではどのような動作をしているのでしょうか?
プログラムは基本的に記憶装置内に保存されています。
記憶装置は大きく「主記憶装置」「補助記憶装置」に分類されます。
- 主記憶装置
- CPUが演算を際に直接読み書きが行えるデータが置かれる
補助記憶装置よりもアクセス速度が高速だが、電源が切られるとデータが揮発する
一般的にはメモリが該当する - 補助記憶装置
- 電源が切れている状態でもデータの保存が可能な装置
主記憶装置よりもアクセス速度が低速
HDD、SSD、USBメモリ、CDなどが該当する
実行前のプログラムは補助記憶装置に保存されています。
プログラムが実行されると補助記憶装置に保存されていたプログラムが主記憶装置に展開されます。
CPUが処理を行う際に直接データにアクセスできるのは主記憶装置だけなので、プログラムは必ず主記憶装置に必ず展開されます。
また、プログラム内で確保したデータ(変数など)も主記憶装置に置かれます。
##1.3. メモリ
メモリとはCPUが処理を行う際に読み書きを行うデータを格納する領域です。
メモリの最小単位は1バイト(※1)であり、1バイトごとにアドレスと呼ばれる識別番号を持っています。
アドレスの最大長がアドレス空間の大きさを決定しており、アドレス長が32bitの場合アドレス空間の大きさは $2^{32}$ となります。
$2^{32}$を10進数、2進数、16進数で表記した場合は以下の通りです。
$2^{32}$ = 4294967296(10進数) = 11111111111111111111111111111111(2進数) = FFFFFFFF(16進数)
【例】
・Windows 10 32bit → アドレス空間の大きさは $2^{32}$
・Windows 10 64bit → アドレス空間の大きさは $2^{64}$
※1 環境により単位は異なる場合があります
##1.4. プログラムによるメモリの使い方
プログラムがメモリ上に展開された際のメモリの使い方について説明します。
プログラムがメモリ上に展開された際にはメモリ内の領域をだいたい以下の4つくらいに分割します。
- テキスト領域
- プログラム自身が格納される領域
- 静的領域(data領域・bss領域)
- グローバル変数などの静的な変数が格納される領域
- ヒープ領域
- プログラム内で動的に確保したデータが格納される領域(NEWなどで作成されたインスタンスとか)
- スタック領域
- 関数の引数やローカル変数などが格納される領域
使用するプログラミング言語により若干異なる場合がありますが、だいたいこんな感じです。
プログラムで主に意識する必要があるのはヒープ領域とスタック領域です。
プログラミング言語によってはヒープ領域に確保したデータは明示的に解放を行わない限り永遠に残り続けることもあります。
この事をメモリリークといいます。メモリリークの発生しているプログラムは長期間動作を行うと確保する領域が不足し、クラッシュしてしまうことが良くあります。
■ 明示的に解放する必要のある言語・・・C、C++、Objective-C など
■ 明示的に解放する必要のない言語・・・Java、C#、VB など
ヒープ領域に確保したデータを明示的に解放する必要の有無はガベージコレクションの機能の有無によって決まります。
ガベージコレクションとはプログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に判断し解放する機能であり、ガベージコレクション機能が搭載されている言語はメモリに確保したデータを明示的に解放する必要はありません。
より詳しく知りたい方は下記URLを参照してください。
http://keens.github.io/blog/2017/04/30/memoritosutakkutohi_puto/
http://ufcpp.net/study/computer/MemoryManagement.html#abstract
確保した変数のアドレスを表示するサンプルプログラム(C++)
#include <iostream>
using namespace std;
int main() {
// スタック領域にint型の変数を2つ確保
int stackVal1 = 0;
int stackval2 = 0;
// int型のポインタ変数を2つ宣言
int *heapVal1;
int *heapVal2;
// ヒープ領域にint型の変数を2つ確保
heapVal1 = new int(0);
heapVal2 = new int(0);
// スタック領域に確保した変数のアドレスを表示
cout << "stackVal1のアドレスは " << hex << &stackVal1 << endl;
cout << "stackVal2のアドレスは " << hex << &stackval2 << endl;
// ヒープ領域に確保した変数のアドレスを表示
cout << "heapVal1のアドレスは " << hex << heapVal1 << endl;
cout << "heapVal2のアドレスは " << hex << heapVal2 << endl;
// 確保したヒープ領域を解放
delete heapVal1;
delete heapVal2;
}
stackVal1のアドレスは 006FFE54
stackVal2のアドレスは 006FFE58
heapVal1のアドレスは 0096DFC0
heapVal2のアドレスは 0096DF70
実行結果のアドレスは実行毎に変わります。また、実行環境やコンパイル時の設定によっても違ってきます。
この結果はRelease x86にてコンパイルを行い、Windows10(64bit)上で実行した結果です。
この結果では、stackVal1とstackVal2では4バイト、heapVal1とheapVal2では80バイトアドレスがズレている事がわかります。
スタック領域は基本的に連続して領域を確保するため何度実行してもstackVal1とstackVal2の差は4バイトとなります。
しかし、ヒープ領域は連続して確保されるとは限らないため、実行するたびにheapVal1とheapVal2の差が変わっているかと思います。
##1.5. 様々なプログラム言語
世の中には多数のプログラミング言語があり、それぞれに特徴があります。
プログラミング言語を分類分けする際に注目する点はいくつかあります。
- 高水準言語 or 低水準言語
- 基本的にプログラミング言語と呼ばれるものはすべて高水準言語です
低水準言語は人間には読みづらい系の言語です
- コンパイラ型 or インタプリタ型
- コンパイラ型言語はコンパイルを必要とし、実行時は機械語として動作します
インタプリタ型はコンパイルを必要とせず、実行時は1行ずつ解析・実行が行われます
- 静的型付け or 動的型付け
- 静的型付けの言語ではプログラムの実行前にすべての変数および式のデータ型が決定されます
動的型付けの言語では実際に実行した結果の値を元に変数および式のデータ型が決定されます
- ガベージコレクションの有無
主な言語を比較します。
言語 | 実行形式 | 型付け | ガベージコレクション | 登場時期 | 習得難易度 | コメント |
---|---|---|---|---|---|---|
C++ | コンパイル | 静的 | なし | 1980 | 高 | 習得難易度は高いですが、習得できれば大きな力となります。 |
C# | コンパイル | 静的 | あり | 2000 | 中 | ここ数年で非常に勢力を伸ばしています。Microsoft製ですが最近はマルチプラットフォームで動作します。 |
Java | コンパイル | 静的 | あり | 1994 | 中 | 特にWeb系で使用される事が多い。これを覚えれば職には困らない。 |
PHP | インタプリタ | 動的 | あり | 1995 | 低 | Web系でしか使われているところを見たことがありません。習得は容易ですが、ソースが読みづらくなりがち。 |
Python | インタプリタ | 動的 | あり | 1991 | 低 | AI関連でデファクトスタンダードになりつつあり、最近勢いがあります。誰が書いても綺麗に書けるとの噂。 |
Ruby | インタプリタ | 動的 | あり | 1995 | 低 | 日本人製の言語で、日本語でのドキュメントが多い。WebフレームワークのRuby on Railsが有名。 |
Visual Basic .NET | コンパイル | 静的 | あり | 2001 | 中 | 嫌い |
詳しくは下記URLを参照してください。
プログラミング言語一覧
2. アドレスとポインタ変数について
ここではアドレスとポインタ変数について学びます。
ポインタ変数については使用できる言語とできない言語がありますが、ポインタ変数が使用できない言語についてはポインタ変数が隠蔽された状態であり、考え方はどの言語でも共通です。
ポインタ変数を使用できる主な言語として、CやC++が挙げられますがそれらの言語において最も挫折しやすいポイントとなっています。
2.1. ポインタ変数とは?
今までの説明でプログラムにて使用するデータはメモリ上に格納されていることを説明しました。そして、メモリには1バイトごとにアドレスが付与されていることも説明しました。
ポインタ変数とはそのアドレスを格納する変数となります。
sample01.ccpの10行目と11行目で宣言している int *heapVal1
と int *heapVal2
がポインタ変数です。
ポインタ変数がどのように動作しているのかを下記のsample02にて確認していきます。
#include <iostream>
using namespace std;
int main() {
// int型のポインタ変数を宣言
int *p;
// ヒープ領域にint型の変数領域を確保し、int型のポインタ変数へ確保したアドレスの先頭番地を代入
p = new int(0);
// heapValの参照先のint型の値を表示する
cout << *p << endl;
// heapValの参照先のint型へ値を代入する
*p = 10;
// heapValの参照先のint型の値を表示する
cout << *p << endl;
// 確保したヒープ領域を解放
delete p;
}
0
10
sample02でを実行した際にメモリ上では以下の図のように状態が変化していきます。
ポインタ変数は実際の値が格納されているアドレスを保持できる変数です。
ここで言う実際の値とはnewで作成したint型の変数であり、この実際の値のことをインスタンス(実態)と呼びます。
そのため、ポインタ変数のサイズはアドレスが格納可能な大きさとなり、32bit環境では4バイト、64bit環境では8バイトとなります。
また、ポインタ変数を使用できない言語においても考え方は同じです。
変数の型によって自動的にポインタ変数として宣言するか、スタック領域に直接変数を作成するのかを判断しています。
2.2. ポインタ変数がない言語での動作
2.1.ではポインタ変数を明示的に宣言し、newで確保したヒープ領域のアドレスを代入していますが、ポインタ変数のない言語では変数の型によりインスタンスのアドレスを格納する変数かどうかを自動的に判断します。
例えばC#では基本的な型(int,floatなど)や構造体は変数を宣言した時点でスタック領域に変数が確保され、newなどでヒープ領域に領域を確保する必要はありませんが、クラスの場合は変数宣言をした段階ではインスタンスが作成されないため、newでインスタンスを作成する必要があります。
using System;
namespace SampleApplication03
{
class classA
{
public int value;
// コンストラクタ
public classA() : this(0) { }
public classA(int initValue) { value = initValue; }
}
class Sample03
{
static void Main(string[] args)
{
// classAのインスタンスのアドレスを格納する変数
classA ca;
// classAのインスタンスの作成 & 変数caへのアドレス代入
ca = new classA(0);
Console.WriteLine(ca.value);
ca.value = 10;
Console.WriteLine(ca.value);
// ガベージコレクションにより確保した領域は解放されるため、
// 解放処理は不要です。
}
}
}
0
10
2.3. 値渡し・参照渡し
関数などの引数として値を受け渡す際に渡し方として渡した値を別の変数にコピーして渡す「値渡し」と渡す値のアドレスを渡す「参照渡し」の2種類があります。
値渡しの場合、引数として渡した元の変数とは全く別の変数に値(インスタンス)がコピーされ関数内ではコピーした変数を使用するため、関数内で引数の値に対して変更を加えた場合でも元の変数の値には全く影響を及ぼしません。
参照渡しの場合、引数として渡した元の変数のアドレスを別の変数にコピーされるため、参照するインスタンスが同じものとなります。その為関数内で引数の値に対して変更を加えた場合は元の変数の値も変更される事となります。
詳しくは下記URLを参照してください。
http://dobon.net/vb/dotnet/beginner/byvalbyref.html
using System;
namespace SampleApplication04
{
class Sample04
{
// 値渡しを行う関数
public static void FunctionA(int inValue)
{
inValue++; // 引数を1増やす
}
// 参照渡しを行う関数
public static void FunctionB(ref int inValue)
{
inValue++; // 引数を1増やす
}
static void Main(string[] args)
{
int a = 0;
int b = 0;
FunctionA(a);
FunctionB(ref b);
Console.WriteLine("値渡しの結果:" + a);
Console.WriteLine("参照渡しの結果:" + b);
}
}
}
値渡しの結果:0
参照渡しの結果:1
3. オブジェクト指向
ここではオブジェクト指向について学びます。
オブジェクト指向とは
【全体草案】
・プログラムサンプル(C++, C#, JAVA)
3章
■オブジェクト指向