データの保存場所とスコープと種類
データを扱う時には以下の4つを考える必要があります。
- 保存場所
- スコープ
- 初期化と終了処理
- 種類(型、タイプ)
保存場所
保存場所には、大きく動的なデータと静的なデータがあり、更に動的なデータにはレジスタ、スタック、ヒープがあり、静的なデータには、ゼロで初期化されたデータ、ゼロ以外で初期化されたデータ、読み取り専用データの種類があります。
- レジスタ(動的)
- スタック(動的)
- ヒープ(動的)
- ゼロで初期化されるデータ(静的)
- ゼロ以外で初期化されるデータ(静的)
- 読み取り専用のデータ(静的)
一般的に静的なデータの方が速度が早く、動的なデータの方がメモリを割り当てたり解放したりする必要があるため、速度が遅いです。しかし、メモリを常に使っていることになるのでメモリ使用の効率が良くないです。
ゼロ以外で初期化されるデータはコンパイルしてできた実行ファイルに含まれ、プログラムが実行時にと実行ファイルからメモリに読み込まれます。ゼロで初期化されるデータは実行ファイルに含まれず、実行時にメモリ領域が作成され、ゼロに初期化されます。
読み取り専用のデータは、ゼロ以外で初期化されるデータとほぼ同じです(OSのセキュリティ機構で書き込みが出来ないよう保護することが可能です)。
動的なデータとしては、速度は一般的にレジスタ > スタック > ヒープ となっています。 その代わり速いほど大きなデータは扱えません。
レジスタはCPU上にある記憶領域で、通常のメモリとは異なります。大抵はメモリ上のデータをCPU上のレジスタにデータを読み込んでから計算します。レジスタはCPUによって数が決まっており少量です。
スタックは、プロセスやスレッド毎に作成されるメモリ領域です。そのサイズはOSやコンパイラによって違います。スタックに複数のデータが追加されたり、削除されたりしますが、順番が決まっています(最後に追加されたデータから削除しないといけない)。また、スタックのサイズは一度プロセスなどを作成してから変更できず、そのサイズを越えて追加が行われるとメモリ違反エラーになるなどの問題が起きます。
このスタックには通常のデータだけでなく、関数の引数やリターンアドレス等のデータも一緒に含まれます。
ヒープはOS等に必要なメモリサイズを要求して、取得されるメモリです。大きいサイズが取得でき、またサイズを変えることができるなど自由度が高いです。大きすぎる場合には取得時にOSがエラーを返すので、エラー処理を記述する必要があることや、解放が必要なため、メモリーリークが起きやすいなど、扱いが難しい部分もあります。
スコープ
保存場所がコンピュータの仕組み的な区別なのに対して、スコープはプログラミング手法での扱い(読み取りや書き込みのアクセス手段)の区別です。例えば以下があります。
- グローバル(どこからでもアクセス可能)
- 同一ファイル内からのみアクセス可能
- 関数内からのみアクセス可能
- 同じ名前空間内からのみアクセス可能(C++のみ)
- 同じクラスからのみアクセス可能(C++のみ)
初期化と終了処理
C/C++言語ではデータに値が入っているとは限りません。これはどんな値が入っているかわからないという意味で、不定値と言います。
C/C++言語では初期化を行う必要があるのですが、暗黙的に初期化が行われたり、明示的に初期化が行われたり、その書き方が様々です。
他の言語では不定値を持つことは少ないのですが、C/C++言語は速度を犠牲にしないために、初期化のタイミングを自由にできるようにするために不定値を持ちます。しかし、不定値は原因が特定しにくい不具合を生みやすいです。
終了処理も場合によっては行う必要があり、行うのを忘れると不要になったメモリがずっと使われている状態(メモリリークと言う)の問題を起こすことがあります。
種類(型、タイプ)
C/C++言語は、データの種類であるデータ型を意識してプログラムする必要があります。プログラミング言語によっては、型を意識することが少ない言語もあるのですが、C/C++言語は強い型付けがある言語です(C言語よりC++言語の方が強い)。
型は、例えば整数なのか、画像なのか、どのようなデータを持っているのかを表すという目的と、データで行われる処理が正しいかをチェックする目的があります。
型により適切なデータ量のメモリを使用することになります。
それと、例えば、整数と画像を足し算すると言うような処理は異常ですが、そのような間違えた処理が行われた場合に、静的に(コンパイル時に)チェックを行い、間違いを知らせることができます。
色々なデータの宣言方法
C/C++言語では、型を宣言することで、データを使用することが出来るようになります。その宣言方法で、「保存場所/スコープ/初期化と終了」が変わってきます。
以下にその宣言方法の例と、「保存場所/スコープ/初期化と終了」について記述します。型については数が多いので、一般的な整数型であるint型を使用して、その他の型については後述とします。
int dataA; int dataB = 1; static int dataC; const int dataD = 2; const static int dataE = 3; void function() { int dataF; int dataG = 4; static int dataH; const int dataI = 5; const static int dataJ = 6; registor int dataK; auto int dataL; dataG += 1; { int dataM; } int dataN; for(int dataO = 0; dataO < 10; dataO++) { } int *dataP = (int i*) malloc(sizeof(int)); int *dataQ = new int; free(dataP); delete dataQ; }
dataAからdataQまで値の定義をしていますが、これらは「保存場所/スコープ/初期化と終了」が以下のようになります。
値 | 保存場所 | スコープ | 初期化と終了 | 備考 | |
---|---|---|---|---|---|
dataA | 静的 | グローバル | 0で初期化 | ||
dataB | 静的 | グローバル | 指定した値(1)で初期化 | ||
dataC | 静的 | 同一ファイル内 | 0で初期化 | 初期値指定も可。C++ではstaticでなく名前空間(namespace)を奨励です。 | |
dataD | 静的(読み取り専用) | グローバル | 指定した値(2)で初期化 | C++では初期値指定必須。 | |
dataE | 静的(読み取り専用) | 同一ファイル内 | 指定した値(3)で初期化 | C++では初期値指定必須。C++ではstaticでなく名前空間(namespace)を奨励です。 | |
dataF | レジスタまたはスタック | 同一関数内 | 不定値 | ||
dataG | レジスタまたはスタック | 同一関数内 | 指定した値(4)で初期化 | ||
dataH | 静的 | 同一関数内 | 0で初期化 | ||
dataI | 静的(読み取り専用)またはスタック | 同一関数内 | 指定した値(5)で初期化 | C++では初期値指定必須 | |
dataJ | 静的 (読み取り専用) | 同一関数内 | 指定した値(6)で初期化 | C++では初期値指定必須 | |
dataK | レジスタまたはスタック | 同一関数内 | 不定値 | 初期値指定も可。レジスタ優先だが、あまり意味はないので通常registorは使用しない。 | |
dataL | レジスタまたはスタック | 同一関数内 | 不定値 | 初期値指定も可。あまり意味はなく、通常autoは使用しない。C++11以降はautoは別の意味で使用される。 | |
dataM | レジスタまたはスタック | }で閉じるまで | 不定値 | その他、初期値指定、static指定など関数初めの宣言と同じ指定も可。 | |
dataN | レジスタまたはスタック | 同一関数内 | 不定値 | C95以前は、関数や演算以降は宣言不可。C99以降、C++ではその他、初期化、static指定など関数初めの宣言と同じ指定も可。 | |
dataO | レジスタまたはスタック | forの{}内 | 指定した値(0)で初期化 | C95以前は、for内で宣言不可 | |
*dataP | ヒープ | dataPと同じ | 不定値。free()関数で終了するまでメモリを使用する。 | ||
*dataQ | ヒープ | dataQと同じ | コンストラクタで初期化。deleteで終了するメモリを使用する。 | C++のみで可能で、C言語では不可 | |
dataP | レジスタまたはスタック | 同一関数内 | 指定した値(maloc()関数の返り値)で初期化 | データの先頭アドレスが保存される。ここではdataMのように指定したが、グローバルにすることもstaticやconstにすることも可能。 | |
dataQ | レジスタまたはスタック | 同一関数内 | 指定した値(newの返り値)で初期化 | データのアドレスが保存される。ここではdataMのように指定したが、グローバルにすることもstaticやconstにすることも可能 |
- 静的なデータは初期化されると考えてください。
- constは定数の意味ですが、C/C++言語では読み取り専用と同等の意味で考えて良いです。
- レジスタかスタックかは通常はコンパイラが決めるため、指定の必要はないです。
- ヒープに関してはポインタの知識が必要ですが、ポインタについての説明は後日予定です。
- ヒープ以外はスコープから抜けるとメモリが解放されますが、ヒープは明示的にメモリを解放する必要があります。
- for分で使っている10などのリテラルの値は静的(読み取り専用)となります。
- 同一ファイル内のスコープとするためにstaticが使われますが、意味が不明(staticは静的の意味)のため、C++では同様の目的で名前空間を使用するのが奨励です。名前空間についての説明もまた後日予定です。
- C言語のバージョン C95以前には、型宣言の場所が先頭のみとなっており、関数や演算の後には型宣言が出来ません。dataMのように{}内であれば、その先頭で型宣言が可能です。
1 | 準備(開発環境のインストール) |
2 | 準備(コマンドライン) |
3 | 準備(テキストエディタ) |
4 | Hello Worldと文法 |
5 | Hello World が動く仕組み |
6 | Hello World ウィンドウ表示(Windows) |
7 | Hello World ウィンドウ表示(GTK+) |
8 | 階乗の計算 |
9 | 数値と名前と変数 |
10 | 演算 |
11 | 処理の流れ その1 |
12 | 処理の流れ その2 条件分岐 |
13 | 処理の流れ その3 繰り返し |
14 | 処理の流れ その4 並列処理 |
14 | データの扱い その1 |
0 件のコメント:
コメントを投稿