アプリケーションでは、様々な外部ファイルを利用します。
ゲームでも、
・テクスチャ
・モデル
・アニメーション
・サウンド
・セーブデータ
…他にも数えきれないほど
保存されているデータをアプリケーションで利用する為には、
まずアプリケーションでファイルを読み込む(ロードする)事が必要です。
ここではC言語におけるアプリケーションでの読み込み方、
そして外部ファイルに保存する方法を紹介します。
ファイルを扱う為に事前に必要な知識
C言語のコーディングでファイル操作を行う為には、
事前に把握しておいた方が良い内容が幾つかあります。
これらを整理した上でコーディングに進みましょう。
ファイル操作の手順
①開く → ②読み取るor書き込む → ③閉じる
どの様なファイルでもこの手順を踏みます。
ファイルの種類
ファイル操作で扱うファイルには二種類あります。
・テキストファイル
・バイナリファイル
テキストファイル
テキストファイルは「文字のみを扱うファイル」です。
正確には「保存されている数値を必ず文字を表す番号として扱うファイル」となります。
単純な構造であるため、人の目で見て把握しやすく、
どのような環境でも利用しやすいです。
ただし「文字を表す番号」には色々種類があります。
・UTF-8
・Shift_JIS
など
これらは文字コードと呼ばれます。
バイナリファイル
バイナリファイルは「コンピュータが扱いやすい形になっているファイル」です。
バイナリファイルは人の目から見てぱっと見でどんなデータか判断が付きません。
2進数の塊で出来ているデータとなります。
単なる数値でしかない為、その数値をどう扱うかはプログラム次第になります。
「41」という数字が入っていても、
それが「文字」なのか「数値」なのかは、
ファイルのデータのみでは判断できません。
FILE構造体
下記の図の様に、ハードディスクからファイルをメモリ側に持ってくる際、
ストリームと呼ばれる部分を経由し、FILE構造体を通じて初めて利用できるようになります。
モード
ファイルの扱い方を指定する為の文字列です。
ファイルを
・読み込むのか
・上書きするのか
・最後尾から追加するのか
等を指定する為に必要になります。
文字列と対応する扱い方は下記の通りです。
文字列 | 内容 | ファイルが既にある場合 | ファイルがまだない場合 |
---|---|---|---|
r | 読み込む | 読み込み成功する | 読み込み失敗する |
w | 書き込む | 内容が全削除される | 新規作成される |
a | 追加で書き込む | 末尾に追加される | 新規作成される |
r+ | 読み込む/書き込む | 読み込み成功する | 読み込み失敗する |
w+ | 読み込む/書き込む | 内容が全削除される | 新規作成される |
a+ | 読み込む/追加で書き込む | 末尾に追加される | 新規作成される |
また、扱いたいファイルの種類によっても文字列が変化します。
・テキストファイル:上記の文字列で問題ありません。
・バイナリファイル:文字「b」を追加し、「rb」「wb」の様に指定します。
EOF
ファイルの終端を表すキーワードです。
ファイル操作を行う一部の関数では、
ファイルの末端まで読み込んだ or 書き込んだ際に、
戻り値でEOFというキーワードを返します。
このキーワードが返されれば、そのファイルはそれ以上操作出来ない
という意味を指す為、ファイル操作の終了させる条件に利用されます。
バイトオーダー
メモリには「どの順番で情報が入るか」というルールがあり、
それをバイトオーダーと呼びます。
「リトルエンディアン」「ビックエンディアン」の二つを覚えておくといいでしょう。
例として「0xAABBCCDD」の数値を格納した4バイト変数があるとします
その数値の1バイト毎のメモリ格納結果は、バイトオーダーによって変化します。
ビッグエンディアンの場合の内容
・・・| 0xAA | 0xBB | 0xCC | 0xDD |・・・
リトルエンディアンの場合の内容
・・・| 0xDD | 0xCC | 0xBB | 0xAA |・・・
バイトオーダーはプログラムを動かすハードウェア(CPU)によって決まります。
環境によってデータの格納のされ方が変わり、
同じファイル読み込んでるのにデータが違う、という事もしばしば起こりますのでご注意ください。
アラインメントとパディング
突然ですが、下記構造体のサイズはいくらでしょうか?
// 構造体(サイズは10?)
struct Str
{
char a;
char* b;
int c;
char d;
};
10と答えたいところですが、実際は10ではありません。
それぞれの変数型には、アラインメントと呼ばれる数値があり、
メモリ確保時には、その倍数のアドレスに配置されるルールがあります。
下記は 32bit環境 での一例です。
型 | サイズ(バイト) | アラインメント |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
ポインタ | 4 | 4 |
各型の場合は上記の様にわかりやすいですが、
構造体の場合は少し特殊です。
構造体が持つ内容の中で一番大きいアラインメント値が構造体自体のアラインメント
になります。
アラインメントを考慮して配置された際に生まれる「空」の領域をパディングと呼びます。
実際は下記の様にパディングが追加され、最終的なサイズは16になります。
// 構造体(32bit環境でパディング込みの場合)
// 64bit環境では結果が変わる
struct Str32
{
char a;
// ---- ポインタ型は4バイトアラインメント、その為直前にパディングが追加される
char padding1[3]; // ← コード上には存在しない
char* b;
// ---- int型 は4バイトアラインメント、元々4バイト境界に位置する為パディングはなし
int c;
// ---- char型 は1バイトアラインメントは、元々1バイト境界に位置する為パディングはなし
char d;
// ---- 構造体の終わりだが、構造体自体のアラインメントは、一番大きい値に合わせて4バイトとなる
// ---- Str32型 の4バイトアラインメント、その為最後のパディングが追加される
char padding2[3]; // ← コード上には存在しない
};
構造体毎にファイルの内容を書き込んでいく際、
構造体のサイズが思ってたのと違う?といった事も出てくるかもしれません。
そんな時は、まずアラインメントやパティングを思い出してもらえればと思います。