目的
C言語のポインタ受け渡しでドはまりしたので、繰り返さないようにログを残し、事象を整理する。
問題
関数内で設定している構造体の文字列データが、別の処理で行っているmemset
のタイミングで壊れてしまう。
原因
戻り値の構造体が保持している変数に、関数内で定義した変数のポインタを渡してしまったことが原因でした。
// 人物情報構造体
typedef struct {
char *name;
int age;
} PERSON;
// 人物情報を取得する関数
// target:txtファイルを'fgets'関数で読み込んだ一行分の文字列データが格納されている
PERSON GetPerson(char *target) {
// CSV文字列(target)を分解し、構造体へ格納して値を戻す
char *splitted[2];
// CSV分解関数(cclip様の分割関数を利用)
split(target, ",", splitted, 2);
int age = atoi(splitted[1]);
// 戻り値を設定
PERSON result = { splitted[0], age }; // <- ここで関数内で定義した変数を戻り値の変数に格納
return result;
}
上記で記載したGetPersonによる構造体取得処理は、戻り値とする構造体に関数内で取得した変数のポインタを渡しています。
この関数は値が設定された人物構造体を戻すのですが、構造体メンバのname
はスタック領域の状態により値が変わってしまう状態になっています。
そのため、別関数などでmemset
等のメモリ値を変更する関数が呼ばれ、splitted
を格納しているスタック領域が書き換えられた場合、構造体内のname
で保持している値が壊れてしまいます。
(というか実際に壊れてしまいて、原因がわからなくてドはまりしていました)
対応
あらかじめ作成していた構造体変数をポインタ引数として取得し、関数内で値を変更するようにしました。
// 人物情報構造体
typedef struct {
char name[256];
int age;
} PERSON;
// [改良版]人物情報を取得する関数
// target:txtファイルを'fgets'関数で読み込んだ一行分の文字列データが格納されている
// person:呼び出し側で初期化済みの人物構造体のポインタ
void GetPerson(char *target, PERSON *person) {
// CSV文字列(target)を分解し、構造体へ格納して値を戻す
char *splitted[2];
// CSV分解関数(cclip様の分割関数を利用)
split(target, ",", splitted, 2);
strcpy(person->name, splitted[0]);
person->age = atoi(splitted[1]);
}
呼び出し側で初期化されている構造体ポインタを引数にとり、随時値を代入することで初期化漏れを防いでいます。
また、構造体の型をchar
配列に変更し、strcpy
で文字列の実体をコピーし、スタック領域が書き換えられても影響がないようにしています。
コメントでsscanf()
を利用した処理を教えていただきました!
bool GetPerson(const char* target, PERSON* person) {
return sscanf(target, "%255[^,],%d", person->name, &person->age) == 2;
}
こちらの方がcsv分割関数も必要なく、すっきりと書けるので見通しも良いです。
今後文字列分解処理をする際は、sscanf()
を利用して文字列分解処理をしようと思います。
@fujitanozomu さん、コメントいただきありがとうございます!
おわりに
自身でも意識していない箇所でポインタ先メモリの書き換えが行われており、大はまりしました。
初めてのC言語のため、メモリ管理が必要、クラスが存在しない、ポインタに受け渡しによる参照等、意識することが多くて難しいです。
今回の問題も、今まで意識してこなかったスタック領域が書き換わったために発生したバグで、非常に勉強になりました。
「ここはこうした方が良い」等のご意見がございましたら、教えていただけると嬉しいです。