パート5:必要な間、必要な量だけメモリを確保する
 100文字程度で制限となる暗号化アプリケーションでは実際の役には立ちません。
 1万文字でも暗号化できるようにしてみましょう。
 といってもCipherText構造体を
typedef struct {
int shift;
char text[10000];
} CipherText;

 とするわけではありません。
 無駄が多いですし、スタック領域は、むやみに巨大な領域を確保するべき場所でもないからです。
スタックオーバーフロー
 以前も話しましたがスタック領域は、全メモリ容量の1/100~1/1000程度におさえられています。そのため巨大なローカル変数配列を宣言したりすると、スタック領域を突き破ってしまう(スタックオーバーフローと呼びます)場合があります。

例) char a[1000000];

 このような場合も、この後で紹介するmalloc関数を使って、スタック領域外に変数用のメモリ区画を確保します。

例) char* a = malloc(1000000);

 今回のように、暗号化する文字列の長さは事前にわからないというような場合は、文字列用の記憶エリアを配列として確保する事をやめ、ポインタ型に変更します。
typedef struct {
int shift;
char* text;
} CipherText;

 ただしポインタ型は番地を保持するだけなので、このままでは何の役にも立ちません。
 役に立たないどころか、スタック領域に確保した変数は、値を代入するまでは、どのような値が入っているかは不定なので、危険でさえあります。

テン*シー*シー-1

malloc
 text要素が指す番地を有効な番地にする必要があります。
 このために使うのがmalloc関数で、この関数は、引数に指定されたバイト数分のメモリ区画を、ヒープ領域に確保し、その先頭番地を戻します。

テン*シー*シー-2

 確保するメモリの大きさはバイトで指定しなければなりません。
 そのため、char型のエリアを100個用意したい場合
バイト数 = char型が必要なバイト数 * 要求数

 という計算が必要になります。

sizeof( )
 char型だから1バイト、int型なら4バイト、などとはせずに型が必要とするバイト数を問い合わせるsizeof( ) 演算子を使います。
 例えばint型の区画を100個確保したい場合は
malloc(sizeof(int) * 100);

 と記述します。

$テン*シー*シー-5

キャスト
 mallocから戻される番地はヒープ領域に確保したメモリ区画の先頭番地であり、その型は不明な状態です。
 void*型のままだと、ポインタ型で説明した++や--を使おうとしても、コンパイラは型がわからないので、結局1バイトごと増減させる事になります。
 確保した区画をint型が連続した区画として利用したいなら、戻された番地をvoid*型ではなくint*型として使う事をコンパイラに指示するのがキャスト(cast:配役)演算子です。

テン*シー*シー-3

 以下ではvoid*型からint*型に型変換しています。
int* p = (int*)malloc(sizeof(int) * 100);

暗黙のキャスト
 もっともmallocが戻すvoid*型から別のポインタ型への代入は、特にキャストを明記しなくてもコンパイラは注意してきません。void*型は不明な型のポインタ型なので、どの型のポインタ型に代入しても間違いではないからです。
 コンパイラは自力で解決可能な場合は暗黙のうちにキャストをおこないます。
int* p = malloc(sizeof(int) * 100); ←特に何も注意されない。

 例えばchar型からint型への代入は
 char a = 1;
int b = a;

 と書くだけでいいのですが、実際は
 char a = 1;
int b = (int)a;

 というキャストが暗黙でおこなわれています。
 char*型やint*型といった、型のはっきりしたポインタ型の代入なら、コンパイラは注意してきます。
 char* a = "TEST";
int* b = a; ←注意される。

 この場合キャストが必要です。

 char型とint型ではメモリ区画の区切りも違うし、記憶された数値の意味も違います。
 実際に、こんな局面があるのかと思われるかもしれませんが、それを承知で、あえて違う型のポインタでメモリを操作する手法も存在します。


free
 malloc関数でヒープ領域に確保した記憶領域は、ローカル変数のように関数から戻る時に自動的に解放される事はありません。プログラム記述者がfreeという関数を使って解放を指示するまで、メモリに居座ります。
 malloc関数で確保したメモり区画は、用が無くなったらfree関数で解放する事を忘れないでください。

テン*シー*シー-4

 またmallocやfree関数の宣言はstdio.hではなくstdlib.hに入っているので、こちらも#includeする必要があります。
#include <stdlib.h>

 以下にmalloc、freeを使ったCipherText構造体利用例を示します。
#include <stdio.h>
#include <stdlib.h> ←malloc、free関数を使うのに必要。

typedef struct {
int shift; // ずらし量
char* text; // 暗号化された文字列
} CipherText;

// textで渡された文字列の文字数を数える。
static int stringlen(const char* text)
{
int max_length = 0; // 文字数カウントを0に初期化
while (text[max_length] != 0) {
max_length++; // 終端ではないのでカウントを増やす。
}
return max_length;
}

 最初にmallocやfree関数を使うためにstdlib.hを#includeしています。
 そしてCipherText構造体のtext要素はchar*型になっています。

 次に用意したstringlen関数は、引数textで受け取った文字列の文字数を返す関数です。
static int stringlen(const char* text)

 これまでの応用なので特に説明はしません。自分で動作を解析してみてください。
 C標準ライブラリにstrlenという関数が用意(string.hで宣言)されているので、本来ならそちらを使うべきです。ここでは、C文字列の扱い方の復習用に用意してみました。

 この関数は、次のcipher、decipher関数でmallocに必要なバイト数を決めるために利用します。
 cipher関数では、最初に渡されたplain_textの文字数を、先のstringlen関数で調べ、暗号化された文字列を記憶するのに必要な大きさを計算しmallocで確保するようにします。

メモリリーク
 以前にmallocしたものがあれば、先にfreeで解放するようにもしています。
 この作業をおこたると、cipher関数が呼ばれるたびに2度と使われないメモリ区画ができていく事になります。
 これをメモリリーク(漏洩)と呼びます。

NULL
 以前にmallocしたものがあるかないかは
if (cipher->text != NULL)

 で判断します。
 ここで使われているNULLは0番地を意味します。
 malloc関数はメモリ区画の確保に失敗すると、このNULLを戻すようになっているので、NULL以外が設定されていれば、有効な番地とみなして良い事になります。
 mallocでメモリ区画の確保に失敗したら作業が続けられないので、cipher関数から戻ります。
 cipher関数は、暗号化に失敗したか成功したかがわかるように値を戻すようにし、失敗した場合は0、成功した場合は1を戻すようにしました。

$テン*シー*シー-6

int cipher(const char * plain_text, int shift, CipherText * ioCipher)
{
int length = stringlen(plain_text);
if (ioCipher->text != NULL) { ← 以前に確保した領域があるので、先に解放する。
free(ioCipher->text);
}
ioCipher->text = malloc(sizeof(char) * (length + 1)); ← 新しく領域確保。
if (ioCipher->text == NULL) { ← 確保に失敗。
return 0; ← 失敗
}
ioCipher->shift = shift;
for (int i = 0; i < length; i++) { ← lengthが全文字数。
ioCipher->text[i] = *plain_text + ioCipher->shift;
plain_text++; ← 次の文字にすすめる。
}
ioCipher->text[length] = 0; ←終端を設定。
return 1; ← 成功
}

 cipher関数呼び出し側で、この戻り値をチェックして、その後の動作を決定します。
 人によっては
if (ioCipher->text != NULL)

 とせずに
if (ioCipher->text)

 と記述する人もいます。条件式は最終的にbool値として評価されるのですが、NULLはfalse、それ以外はtrueとなるため、上の記述はどちらでも同じように動作します。

 また、ioCipher->textがNULLかどうかの評価の前提として、ioCipher->textの初期値がNULLでなければなりません。
CipherText a;
a.text = NULL;

 とすればいいだけですが、ここではもう少し明示的に、CipherText構造体の初期化関数を用意する事にしました。
void initCipherText(CipherText* cipher)
{
cipher->shift = 0;
cipher->text = NULL;
}

 同時に、同じ理由で、使い終わった暗号文字列メモリ区画を解放する関数も用意します。
void deleteCipherText(CipherText* cipher)
{
if (cipher->text != NULL) {
free(cipher->text);
cipher->text = NULL;
}
}

 main関数では
int main (int argc, const char * argv[])
{
CipherText a;

initCipherText(&a);
cipher("HELLO WORLD", 3, &a);
deleteCipherText(&a);
return 0;
}

 というようにして利用します。
 そして、cipherに設定されている暗号文字列を解凍するdecipher関数は、暗号解凍後の文字列をmallocで確保して戻すようにします。メモリ区画確保に失敗した場合は、malloc関数同様NULLを戻します。
char* decipher(const CipherText * cipher)
{
int length = stringlen(cipher->text);
char* text = malloc(sizeof(char) * (length + 1)); ← 新しく領域確保。
if (text == NULL) {
return NULL; ← 失敗
}
↓ 暗号を解く
for (int i = 0; i < length; i++) { ← lengthが全文字数。
text[i] = cipher->text[i] - cipher->shift; ← 1文字、暗号を解く。
}
text[length] = 0; ← 終端を設定。
return text; ← 成功
}

 当然、decipherを呼び出した側には、戻された文字列メモリ区画を使い終わったらfreeで解放する義務があります。
 最終的なmain関数は以下のようにします。
#include <stdio.h>
#include <stdlib.h>
・・・
typedef struct {
int shift; // ずらし量
char* text; // 暗号化された文字列
} CipherText;
・・・
static int stringlen(const char* text)
{ ・・・
}
void initCipherText(CipherText* cipher)
{ ・・・
}
void deleteCipherText(CipherText* cipher)
{ ・・・
}
int cipher(const char * plain_text, int shift, CipherText * ioCipher)
{ ・・・
}
char* decipher(const CipherText * cipher)
{ ・・・
}
int main (int argc, const char * argv[])
{
CipherText a;

initCipherText(&a);
if (cipher("HELLO WORLD", 3, &a) == 1) { ← 暗号化成功
char* plain = decipher(&a); ← 暗号解凍
if (plain != NULL) { ← 解凍成功
printf("%s = %s\n", plain, a.text);
free(plain); ← 戻された暗号解凍文字列のメモリ区画を解放する。
}
}
deleteCipherText(&a); ← 暗号文字列のメモリ区画を解放する。
return 0;
}

サンプルプロジェクト:study-20-cipher-malloc.zip

 これでコンピュータ自身のメモリ容量のみに制限される暗号化アプリケーションが出来上がったわけです。
 最後に、この暗号関連の関数群を別のファイルに分離してみましょう。

 次回に続く~。