VC7.1 には (VC7.0にも?) 安全に文字列操作を行うための関数群が追加されました。
今まで文字列操作をするとなると strcpy
や lstrcpy
等を使っていましたが、これらはいとも簡単に確保したバッファ以上の領域に文字列を書き込んでしまいます。
領域以上に書き込んでしまい、アプリが落ちるだーの、ほかの変数の値がおかしくなるだーの…ということは、Cプログラマであれば誰もが経験をしている事でしょう…^^;
また、このようなバッファオーバーランはセキュリティホールとなる可能性が強いです。その為、事故を減らす意味でも、strcpy
などの関数の使用はオススメできません。
がー、そうなると、文字列操作するための関数がなくなってしまいます。仕方なしに自前で安全な文字列操作関数を製作するのも良いでしょうが…… 自分で打ったソースコードってのは絶対に信用できない訳です。(ぉ
そこで(?)、Microsoft自身がオーバーランをしないよう考慮した関数を作ってくれました。その関数群が入っているのが strsafe.h です。
strsafe.h で定義されてた新しい関数は以下の30個です。
一見大量にあり「ウボァー」という感じで嫌になりますが、実はそんなに嫌がるものでもなかったりします。
この8つの関数が基本としてあり、それぞれにEx版があり 且つ Cch/Cb系がある… というような感じです。 Length だけは例外で Ex版はありません。
各関数は 「出力先のバッファの量」 を引数に取ります。この時 Cch/Cb とで指定する値の意味が変わっています。次の表のようになっています。
Cch系 | バッファの量を文字数で指定します。 |
Cb系 | バッファの量をバイト数で指定します。 |
これら関数の戻り値は全て HRESULT
です。 よって従来の lstrcpy
や strcpy
とは違って「関数が成功したか、失敗したか」 という処理結果を返す点に注意する必要があります。
特に Length については注意が必要で、戻り値をそのまま文字列の長さとして使用することは出来ません。 そのままキャストして文字の長さとして使用するのは死亡確定ですので注意しましょう。
なお HRESULT
なので、戻り値を SUCCEEDED
, FAILED
マクロの引数として与え、関数が成功したか失敗したかを手軽に判断する事が可能です。
処理を行う時の詳細な動作を指定する事が出来ます。…が、少々ややこしいので別セクションを設けました。 Ex版の関数について を参照してください。
てきとーに書いてみました。
void check(const HRESULT hr)
{
if(FAILED(hr))
puts("--- バッファが足りないッスよ〜。");
}
int main()
{
CUINT nBuffSize = 20;
tchar szDest[nBuffSize];
size_t nNowLength;
HRESULT hr;
// コピー
hr = ::StringCchCopy(szDest, nBuffSize, _T("Manna "));
check(hr);
puts(szDest);
// 連結
hr = ::StringCchCat(szDest, nBuffSize, _T("Rina Ena "));
check(hr);
puts(szDest);
// 文字数取得
hr = ::StringCchLength(szDest, nBuffSize, &nNowLength);
check(hr);
printf("Length = %d\n", nNowLength);
// バッファの量足りないよ。
hr = ::StringCchCat(szDest, nBuffSize, _T("Honoka Koyuki Fuko Tubomi"));
check(hr);
puts(szDest);
puts("");
// sprintf …つか snprintf と同じじゃ…?
hr = ::StringCchPrintf(szDest, nBuffSize, _T("Num = %d"), 169);
check(hr);
puts(szDest);
// sprintf 長すぎ
hr = ::StringCchPrintf(szDest, nBuffSize, _T("String = %s"), _T("Seria Tina May"));
check(hr);
puts(szDest);
getchar();
return 0;
}
Manna Manna Rina Ena Length = 15 --- バッファが足りないッスよ〜。 Manna Rina Ena Hono Num = 169 --- バッファが足りないッスよ〜。 String = Seria Tina
このように、使い方としてはバッファの量を指定する点を除いて、基本的に従来の関数と同じです。ただし、StringCchLength
は長さの取得方法が変わっている為、ちょっと注意ですね。
処理を行うときの詳細な動作を指示したり、より詳細な処理結果を取得できたりします。 その為引数が多く、若干メンドウかもしれません。(ぉ ^^;
非Ex版より引数が3つほど増えています。それぞれの意味は次のとおりです。
LPTSTR* | ppszDestEnd | 処理先の文字列の終端 を取得します。NULLの指定も可。 |
size_t* | [Cch系]pcchRemaining [Cb系]pcbRemaining |
処理を行った後の残りのバッファ量を取得します。NULLの指定も可。 |
DWORD | dwFlgs | 処理を行うときの詳細な動作を指定します。 また、下位1バイトは任意に指定する事が可能で、 STRSAFE_FILL_BEHIND_NULL と STRSAFE_FILL_ON_FAILURE で使用されます。 |
フラグについての詳細は次の「フラグについて」を参照してください。
処理を行うときの詳細な動作を指定する事が出来ます。
なお、STRSAFE_FILL_BEHIND_NULL
, STRSAFE_FILL_ON_FAILURE
は、特別に下位1バイトを使用します。 おそらくサンプルコードを参照した方が解りやすいかもしれません。
STRSAFE_FILL_BEHIND_NULL
STRSAFE_IGNORE_NULLS
STRSAFE_FILL_ON_FAILURE
STRSAFE_NULL_ON_FAILURE
STRSAFE_NO_TRUNCATION
STRSAFE_NULL_ON_FAILURE
と同じです。(多分^^;)
やっぱりコレもてきとーに。
void output(const HRESULT hr, const size_t nRemain, const tchar* pszMsg)
{
if(FAILED(hr))
_putts(_T("--- バッファが足りないッスよ〜。"));
_tprintf(_T("残りBuff = %d\t: %s\n"), nRemain, pszMsg);
}
void dump(const tchar* szOut, CUINT nLength)
{
_putts(_T("--- Buff dump..."));
for(UINT i = 0; i < nLength; ++i)
{
CINT c = szOut[i];
if(c == _T('\0'))
_puttchar(_T('\\')), _puttchar(_T('0'));
else
_puttchar(szOut[i]);
}
_puttchar(_T('\n'));
}
int main()
{
CUINT nBuffSize = 20;
tchar szDest[nBuffSize];
tchar* pszEnd;
size_t nRemain;
HRESULT hr;
// 普通にコピー
hr = ::StringCchCopyEx(szDest, nBuffSize, _T("Manna "), &pszEnd, &nRemain, 0);
output(hr, nRemain, szDest);
// 連結…とはいえ 元が NULL("") なので何も変わらない
hr = ::StringCchCatEx(szDest, nBuffSize, NULL, &pszEnd, &nRemain, STRSAFE_IGNORE_NULLS);
output(hr, nRemain, szDest);
// ↓コレでは、コピー元がNULLで NULLを無視するフラグを立てていないので落ちてしまう。
// hr = ::StringCchCatEx(szDest, nBuffSize, NULL, &pszEnd, &nRemain, 0);
// 連結
// バッファの余った領域は # で埋める。
hr = ::StringCchCatEx(szDest, nBuffSize, _T("Rina Ena "), &pszEnd, &nRemain, '#' | STRSAFE_FILL_BEHIND_NULL);
output(hr, nRemain, szDest);
dump(szDest, nBuffSize);
_puttchar(_T('\n'));
// STRSAFE_NO_TRUNCATION 指定での失敗なので、何もしません。
hr = ::StringCchCatEx(szDest, nBuffSize, _T("Honoka Koyuki Fuko Tubomi"), NULL, &nRemain, STRSAFE_NO_TRUNCATION);
output(hr, nRemain, szDest);
// フラグ指定なしでの失敗
hr = ::StringCchCatEx(szDest, nBuffSize, _T("Honoka Koyuki Fuko Tubomi"), NULL, &nRemain, 0);
output(hr, nRemain, szDest);
// STRSAFE_NULL_ON_FAILURE 指定なので、バッファがNULL文字になる。
hr = ::StringCchCatEx(szDest, nBuffSize, _T("Honoka Koyuki Fuko Tubomi"), NULL, &nRemain, STRSAFE_NULL_ON_FAILURE);
output(hr, nRemain, szDest);
_puttchar(_T('\n'));
// コピーに失敗で、$ で埋める。
hr = ::StringCchCopyEx(szDest, nBuffSize, _T("Honoka Koyuki Fuko Tubomi"), NULL, &nRemain, '$' | STRSAFE_FILL_ON_FAILURE);
output(hr, nRemain, szDest);
// sprintf に失敗で NULL文字列化。STRSAFE_NULL_ON_FAILURE 指定でも同様
hr = ::StringCchPrintfEx(szDest, nBuffSize, NULL, &nRemain,
STRSAFE_NO_TRUNCATION, _T("Num = %d, String = %s"), 169, _T("Seria Tina May"));
output(hr, nRemain, szDest);
getchar();
return 0;
}
残りBuff = 14 : Manna 残りBuff = 14 : Manna 残りBuff = 5 : Manna Rina Ena --- Buff dump... Manna Rina Ena \0#### --- バッファが足りないッスよ〜。 残りBuff = 5 : Manna Rina Ena --- バッファが足りないッスよ〜。 残りBuff = 1 : Manna Rina Ena Hono --- バッファが足りないッスよ〜。 残りBuff = 20 : --- バッファが足りないッスよ〜。 残りBuff = 1 : $$$$$$$$$$$$$$$$$$$ --- バッファが足りないッスよ〜。 残りBuff = 20 :
こんな感じで、失敗時の処理などがちょっと高度に行う事が出来ます。 …まぁでも、あんまり使わないかも。(ぉ
#include <strsafe.h>
することにより、新しい関数を使用できるようになりますが、逆に従来の strcpy
や lstrcpy
が使用出来なくなるような感じになります。
正確には、VC6で利用不可に、VC7.1では「つかっちゃイヤン警告」が出ます。
折角新しくて安全な関数が導入されたんですから、そっちを使用するような方向に持っていくのは当然といえば当然です。…が、時には昔の関数も使用しなきゃいけない時がくるかもしれません。
そんな時は STRSAFE_NO_DEPRECATE
を、#include <strsafe.h>
の前に定義してあげると、従来の関数も同時に使用できるようになります。
#define STRSAFE_NO_DEPRECATE // 前で定義すると混在可能になる
#include <strsafe.h>
いろいろ書いてきましたが…多分こんな感じでしょう。間違ってたらご連絡ください。^^;
…まぁでも、結局のところ、素直に std::string
やら CString
使おうぜ。(今までの文章一気に台無し......)