atol関数に渡す文字列は最後がヌル文字であるべき

素人なバグの話。2つのファイルからそれぞれ1行(1レコード)読んで、キー項目が一致した場合にその中の数値が同じかどうか確かめるという処理を書いていた。数値は前ゼロ埋めやら、スペース埋めやら、正符号付きやらいろいろあったので、atol関数でcharからlongに変換して比較することにした。こんな感じ。

struct T{ // 1レコードを表わすcharの構造体
    .... 
    char ch1[14];
    char ch2[14];
    ....
} 

...
FILE *fp1, *fp2;
T t1, t2;
long lvalue1, lvalue2;
...
// それぞれのファイルから1行分を読んで構造体に格納
fread(t1, sizeof(t1), 1, fp1);
fread(t2, sizeof(t2), 1, fp2);
...
// キー一致の確認(省略)
...
// c1の部分だけlongに変換したつもり
lvalue1 = atol(t1.ch1);
lvalue2 = atol(t2.ch2);

// 以下の比較で一致するはずのものが一致してなかった。
if (lvalue1 == lvalue2) {
    ...

lvalue1とlvalue2が一致するべきところが一致しない。なんでだと調べてみたら、実はch1の後ろのch2の1バイト目に数字が入っていた。でatol()がどうなっていたかというと、

lvalue1 = atol(t1.ch1);

ここで変換されてlvalue1に入っていたのは、t1を突っ切ってt2も含めた文字列が変換されていた、という話。調べてみると、

int atoi(char *string);
文字列データとしての数字列をint型に変換。null文字または数字以外の文字で文字列の最後を判断。 (atol()も同様の記述)

http://www.isc.meiji.ac.jp/~re00030/jse/libkansu.html

とある。確かに、どこでやめていいかわかんないよね。

仕方がないので、1バイト多めのchar配列を用意して、memcpy()で先頭からコピーし、最後に'\0'を入れてやるという処理をしてその場はしのいだ。

char tmpstr[15];
memcpy(tmpstr, t1.c1, sizeof(t1.c1));
tmpstr[14] = '\0';
lvalue1 = atol(tmpstr);

ああ、ダサダサだ。もっと上手い常套句があるんだよねきっと。