strncpy_s()への修正で考えさせられた件
vs2005からCRTセキュリティ強化ということで*_s()系の関数が導入されています。でもこれ、よく考えないで修正すると痛い目にあいそう。僕の場合、既存の次のようなコードで躓きました。
typedef struct _dest { char x[3]; char y[5]; char z[5] } dest;ような構造体にデータを読み込み、x,y,zのそれぞれを整数に変換する下記のような処理があったのですが、
char a[6];
int X,Y,Z;
memset(a,0x00,sizeof(a));
strncpy(a,dest.x,sizeof(dest.x));
X = atoi(a);
memset(a,0x00,sizeof(a));
strncpy(a,dest.y,sizeof(dest.y));
Y = atoi(a);
memset(a,0x00,sizeof(a));
strncpy(a,dest.z,sizeof(dest.z));
Z = atoi(a);これを、大して考えもせず機械的に、
memset(a,0x00,sizeof(a));
errno_t ent = strncpy_s(a,sizeof(a),dest.x,sizeof(dest.x));
X=atoi(a);というようにstrncpy_s()に修正したわけですが、当然ながらこれはNGでした。strncpy()を使い慣れた身からすると第4引数のサイズは「コピーする最大文字数」のような感覚があります。つまり、strncpy_s()はstrncpy()に第2引数(出力バッファサイズ)を突っ込んだだけの関数だと思い込んでしまったわけです。
上記のように終端がnullでない場合のdest.xの長さ不定となりとなり、sizeof(dest.x)を超える(可能性が高い)ためエラーとなります。strncpy()と同じような動作にするためには第4引数を_TRUNCATEとする必要があります。
memset(a,0x00,sizeof(a));
errno_t ent = strncpy_s(a,sizeof(a),dest.x,_TRUNCATE);
X=atoi(a);これでエラーはでなくなり、一件落着かといえばそうでもなくて(実は僕はこの落とし穴に落ちたんですが)、修正前のコードはdest.x/y/zを切り出すワークとしてそれぞれのサイズを格納するに十分なchar a[6]を使っていますが、こいつがいけない。dest.xはchar 3ですから_TRUNCATEを指定するとコピーする文字数はsizoef(a)-1(nullを埋める1byteは確保される)もしくはdest.xのnull直前までということになります。つまり、上記の例でいえばdest.yの頭2byteまでコピーしちゃうんですね。dest.xを切り出すためにchar a_x[4]; dest.y/z用にchar a_yz[6];とすれば_TRUNCATEでよいのでしょうが...
つまるところ、str*はsourceがC/C++の文字列(終端がnull)であることが基本の関数ですから、上記の例にように終端が保証されないデータに対してstr*(例えstrn系だとしても)を使っているのが間違いなんですね。既存のコードはやっぱり、
memset(a,0x00,sizoef(a));
memcpy(a,dest.x,sizeof(dest.x);
X=atoi(a);となっているべきだったのかと今は思います。そうすれば今回の修正もすんなりと、
memset(a,0x00,sizoef(a));
errno_t ent = memcpy_s(a,sizeof(a),dest.x,sizeof(dest.x));
X=atoi(a);とできたのではないかと。やれやれ。
無意識のうちこういったコードを書いている可能性は大いにあって機械的な修正はむしろ危険。それにしても自分の未熟さを痛感しました。









最近のコメント
39 weeks 12 hours ago
47 weeks 6 days ago
48 weeks 14 hours ago
48 weeks 21 hours ago
49 weeks 2 days ago
49 weeks 2 days ago
50 weeks 1日 ago
50 weeks 3 days ago
51 weeks 2 days ago
51 weeks 2 days ago