実際にバグが起こったコード
const char* GetColorCommand(COLOR_TYPE _colorType, COLOR_RGB _color) {
char command[32] = "";
/* 文字か背景かのセット */
switch (_colorType) {
case COLOR_TEXT:
strcpy(command, "\x1b[38;2;");
break;
case COLOR_BG:
strcpy(command, "\x1b[48;2;");
break;
}
/* RGB値のセット */
strcat(command, IntToString(_color.r));
strcat(command, ";");
strcat(command, IntToString(_color.g));
strcat(command, ";");
strcat(command, IntToString(_color.b));
strcat(command, "m");
return command;
}
こんなコード。コンソールでゲームを作るために、テキストや背景の色を変えるエスケープシーケンスを楽に取得できる関数として作った。
何が起こった?
Debugビルド時は以下の通り
プレイヤーの(´・ω・`)がちゃんといる。描画も問題なさそうである。
それが、Releaseビルドに変えると
これ。表示バグどころか何も表示されなくなる。
試しに、色変更に関するコードをコメントアウトするとこんな感じ。
モノクロになったがちゃんと表示されている。
解決した方法
const char* GetColorCommand(COLOR_TYPE _colorType, COLOR_RGB _color) {
static char textCommand[256] = "";
static char bgCommand[256] = "";
最初の宣言をこんな感じにすると解決。staticを付けて静的変数にすると上手くいったが、このときはまだ根本原因を理解していなかったため、かなり無理やりな手法。
根本原因
メモリについての理解が足りていなかった。理解できれば超シンプル!
ローカル変数のアドレスを返り値にしていたこと。これだけ。
そもそも、関数内のローカル変数というのは
char str[64];
このように宣言すると自動変数というものになる。自動変数は関数内の処理が終了するとメモリが解放される変数である。つまり、const char* の返り値としてローカルな自動変数のアドレスを指定すると、関数の処理が終了した時点でそのアドレスは無効になってしまう!
言われてみればそりゃそうだ。
そのため、static修飾子をつけて静的変数にすれば関数が終了してもメモリが解放されないため、上手くいったということ。デバッグビルドで平気だったのはメモリ内の値がたまたま残っていただけだった模様。
もっといい解決方法
とはいっても、要は静的変数を高速で何度も上書きしているわけなので不具合が絶対に起こらない!とは言えない。(実際、テキストと背景でバッファを分けないと上手くいかなかったので)
↓下記に他の方法を考えてみた
①引数にバッファを入れておく
単純明快!
void GetCommand(char* _str){
strcpy(_str,"");
// 引数のアドレスをいじる
}
いじる文字列が引数なら、呼び出す側の処理が終わらない限り変数は保持される。
ただ、関数を使うたびに変数宣言して、引数に入れて…としないといけないのが少し面倒ではある。
②std::stringを返り値にする
std::string GetCommand(){
// std::stringを返す
}
こちらがより単純明快。std::stringは文字列型で合ってchar* のようにアドレスではないのでローカル変数を返り値にしても大丈夫。動的にメモリを確保するとのことなので、上手く使わないとメモリリークとかになる…?のかも。
最後に
メモリとかの理解が曖昧だったので、ここで少し知れて良かった。const char* を返り値として使うときは return "あいうえお" など定数を返すのが定石なのかも。