none
_sntprintf_s()で_TRUNCATEを使うと"Run-Time Check Failure #2"がでる RRS feed

  • 質問

  • 開発環境は次の通りです。

    WindowsXP(SP2)
    VC++2005(SP1) MFC(Win32でもOK?)

    下記の様なコードを走らせると、デバッグ実行中に"Run-Time Check Failure #2  - Stack around the variable 'szBuffer' was corrupted"
    がでます。
    ヘルプで_TRUNCATEの使用が出来ないのか確認したところ問題なさそうですし、ためしに_TRUNCATEを1024に変更すると問題なく実行(文字が削られることなく)されます。

    Googleで検索もしてみましたけれど、該当の内容は見つけられませんでした。
    どなたかこのような状況の解決策をご存知の方はいないでしょうか?

    void
    Function(LPCTSTR lpszTextA, LPCTSTR lpszTextB)
    {
        TCHAR szBuffer[1024];

        _sntprintf_s(szBuffer, _TRUNCATE, _T("%s %s"), lpszTextA, lpszTextB);

        // 略
    }

    このコードは実際のコードとは違うので同じエラーが出ないかもしれませんが・・・

    2008年4月22日 6:52

回答

  •  zakio さんからの引用
    こんな感じで試してみてはいかがでしょう?

     

    Code Snippet

    _sntprintf_s<1024>(szCommand, _TRUNCATE, _T("%s %s"), lpszPathName, lpszParameter);


    あ、でもこれが原因だとすると、sizeof(szCommand)/sizeof(TCHAR) で問題なく動くこととの辻褄があいませんね。


    無視してください。すみません。


    2008年4月23日 7:51
  •  GonGon さんからの引用

    ポインタではなく配列なので次のテンプレートが摘要されるので問題ないかと思います。

    失礼しました。ヘルプを読み損ねていました。

     

    それだけでは申し訳ないので、原因を突き止めてきました。

    原因はVisual C++ 2005のCRTのヘッダの不具合です。

    再現条件は文字セットがMBCSであることです。

     

    ※Visual C++ 2005からデフォルトの文字セットがUnicodeに変更されています。TCHARを使った話をする場合はMBCSかUnicodeかを明確にするべきです。

    ※Visual C++ 2008のヘッダは修正されているかも、ちらっと見た程度ですけれど。

     

    MBCS環境において_sntprintf_s関数は_snprintf_s関数のエイリアスになります。

    _snprintf_s関数のテンプレートによるオーバーロードバージョンはstdio.hにて次のマクロとして定義されています。

     


    Code Snippet
    __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_2_ARGLIST(int, _snprintf_s, _vsnprintf_s, __out_bcount(_Size) char, _Dest, __in size_t, _Size, __in_z __format_string const char *,_Format)

     

     

    この長ったらしいマクロはcrtdefs.hにて定義されています。

    Code Snippet
    #define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_2_ARGLIST(_ReturnType, _FuncName, _VFuncName, _DstType, _Dst, _TType1, _TArg1, _TType2, _TArg2) \
        extern "C++" \
        { \
     __pragma(warning(push)); \
     __pragma(warning(disable: 4793)); \
        template <size_t _Size> \
        inline \
        _ReturnType __CRTDECL _FuncName(_DstType (&_Dst)[_Size], _TType1 _TArg1, _TType2 _TArg2, ...) \
        { \
            va_list _ArgList; \
            _crt_va_start(_ArgList, _TArg2); \
            return _VFuncName(_Dst, _Size, _TArg1, _TArg2, _ArgList); \
        } \
     __pragma(warning(pop)); \
        }

     

     

    色づけしてるとおり、_Sizeという名前がテンプレート引数と第2引数で重複しています。

    これにより、テンプレート引数の値は失われ、第2引数で指定した値で処理されます。

    従って、_TRUNCATEと指定した場合は4294967295の長さを持つバッファを与えられたものとして処理することとなり、スタック範囲外のメモリ書き込みで落ちます。

     

    回避策としては、テンプレートバージョンを使用しないこととなるでしょう。

    2008年4月23日 13:16
    モデレータ

すべての返信

  • 再現しませんねぇ。
    再現するコードは掲載できませんか?

    2008年4月22日 10:00
  •  GonGon さんからの引用

        _sntprintf_s(szBuffer, _TRUNCATE, _T("%s %s"), lpszTextA, lpszTextB);

    このコード、引数の数が足りなくないですか?

    第2引数のsizeOfBufferに相当するものがないように見受けられます。

    _TRUNCATEが指定できるのは第3引数のcountです。

     

    もちろん、本来のコードでは正しいけれど、例文として書き出す際にミスした可能性が高いのですが。

    2008年4月22日 14:51
    モデレータ
  • ちょっと長いですけれど一般的な関数だと思いますので掲載します。

    内容は外部のプロセスを実行して標準出力の内容をエディットボックスに表示するというものです。
    例であげているszBufferにあたるものがszCommandになります。
    後半の影響のなさそうな部分(コメントアウトしても同じエラーが出ましたので・・・)は割愛します。

    現在は_TRUNCATEの代わりにsizeof(szCommand)/sizeof(TCHAR)を渡していて、これだとエラー(使用上も含めて)もなくデバッグされます。

    MFCを使っているのでCStringを使えば手っ取り早いのですが、CreateProcessの引数がLPTSTRなので・・・しかたなく
    どうしようもないものなら回避策を講じればいいのですが、少し気になって伺ってみました。

    bool
    CreateProcess(LPCTSTR    lpszPathName,
                  LPCTSTR    lpszParameter,
                  HWND    hStdOut)
    {
        SECURITY_ATTRIBUTES    attrSec;
        STARTUPINFO            startInfo;
        PROCESS_INFORMATION    proInfo;
        TCHAR    szCommand[1024];
        bool    bResult = false;
        HANDLE    hPipe[2];

        memset(&attrSec, 0, sizeof(SECURITY_ATTRIBUTES));
        attrSec.nLength = sizeof(SECURITY_ATTRIBUTES);
        attrSec.bInheritHandle = TRUE;

        ::CreatePipe(&hPipe[0], &hPipe[1], &attrSec, 0);

        memset(&startInfo, 0, sizeof(STARTUPINFO));
        startInfo.cb = sizeof(STARTUPINFO);
        startInfo.dwFlags =
            STARTF_USEFILLATTRIBUTE |
            STARTF_USECOUNTCHARS |
            STARTF_USESTDHANDLES |
            STARTF_USESHOWWINDOW;
        startInfo.wShowWindow = SW_HIDE;
        startInfo.hStdOutput = hPipe[1];
        startInfo.hStdError = hPipe[1];

        _sntprintf_s(szCommand, _TRUNCATE,
            _T("%s %s"), lpszPathName, lpszParameter);

        if (!::CreateProcess(NULL, szCommand,
            NULL, NULL, TRUE, 0, NULL, NULL, &startInfo, &proInfo))
        {
            DWORD    dwError = ::GetLastError();
            CString    strError;

            ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                NULL, dwError, 0, strError.GetBuffer(1024), 1024, NULL);

            strError.ReleaseBuffer();
            strError += _T("\n");

            AddWindowText(hStdOut, strError, strError.GetLength());

            goto    FailureProcess;
        }

        // 中略

        bResult = true;

    FailureProcess:

        ::CloseHandle(hPipe[0]);
        ::CloseHandle(hPipe[1]);

        return    bResult;
    }

    2008年4月23日 0:55
  • ポインタではなく配列なので次のテンプレートが摘要されるので問題ないかと思います。

    template <size_t size> int _snprintf_s( char (&buffer)[size], size_t count, const char *format [, argument] ... ); // C++ only
    template <size_t size> int _snwprintf_s( wchar_t (&buffer)[size], size_t count, const wchar_t *format [, argument] ... ); // C++ only
    2008年4月23日 1:00
  •  GonGon さんからの引用
    ポインタではなく配列なので次のテンプレートが摘要されるので問題ないかと思います。

    template int _snprintf_s( char (&buffer)[size], size_t count, const char *format [, argument] ... ); // C++ only
    template int _snwprintf_s( wchar_t (&buffer)[size], size_t count, const wchar_t *format [, argument] ... ); // C++ only

     

    テンプレートの方が呼ばれていない可能性はないですか?

    例えば、こんな関数があって、

     

    Code Snippet

    void Func(char* ptr) {
        TRACE("Func(char* ptr) was called\n");
    }


    template <size_t N>
    void Func(char (&buffer)[N]) {
        TRACE("Func(char (&buffer)[N]) was called\n");
    }

     

    Func を次のように呼び出すと、

     

    Code Snippet

    char buf[1024];

    Func(buf);

     

    VS2005 ではテンプレートではない方の Func が呼び出されました。

     

    こんな感じで試してみてはいかがでしょう?

     

    Code Snippet

    _sntprintf_s<1024>(szCommand, _TRUNCATE, _T("%s %s"), lpszPathName, lpszParameter);

    2008年4月23日 3:38
  •  zakio さんからの引用
    こんな感じで試してみてはいかがでしょう?

     

    Code Snippet

    _sntprintf_s<1024>(szCommand, _TRUNCATE, _T("%s %s"), lpszPathName, lpszParameter);


    あ、でもこれが原因だとすると、sizeof(szCommand)/sizeof(TCHAR) で問題なく動くこととの辻褄があいませんね。


    無視してください。すみません。


    2008年4月23日 7:51
  • _sntprintf_s<1024>(szCommand, _TRUNCATE, ...
    は同じ結果になりました。

    けれど、
    _sntprintf_s((LPTSTR)szCommand, 1024, _TRUNCATE, ...
    は問題なく実行されます。

    2008年4月23日 9:09
  • _sntprintf_s 以外のところで何か問題があるのではないでしょうか。

    私の環境(VS2005 + XP)では、以下のコードが問題なく動作しています。

     

    Code Snippet

    LPCTSTR lpsz1 = _T("hoge");
    LPCTSTR lpsz2 = _T("fuga");
    const int BUF_SIZE = 1024;
    TCHAR buf[BUF_SIZE];
    _sntprintf_s(buf, _TRUNCATE, _T("%s %s\n"), lpsz1, lpsz2);
    TRACE(buf);
    _sntprintf_s(buf, BUF_SIZE, _TRUNCATE, _T("%s %s\n"), lpsz1, lpsz2);
    TRACE(buf);

     

    BUF_SIZE を 5 とかに変更しても、期待通りの動作になります。

    2008年4月23日 10:30
  •  GonGon さんからの引用

    ポインタではなく配列なので次のテンプレートが摘要されるので問題ないかと思います。

    失礼しました。ヘルプを読み損ねていました。

     

    それだけでは申し訳ないので、原因を突き止めてきました。

    原因はVisual C++ 2005のCRTのヘッダの不具合です。

    再現条件は文字セットがMBCSであることです。

     

    ※Visual C++ 2005からデフォルトの文字セットがUnicodeに変更されています。TCHARを使った話をする場合はMBCSかUnicodeかを明確にするべきです。

    ※Visual C++ 2008のヘッダは修正されているかも、ちらっと見た程度ですけれど。

     

    MBCS環境において_sntprintf_s関数は_snprintf_s関数のエイリアスになります。

    _snprintf_s関数のテンプレートによるオーバーロードバージョンはstdio.hにて次のマクロとして定義されています。

     


    Code Snippet
    __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_2_ARGLIST(int, _snprintf_s, _vsnprintf_s, __out_bcount(_Size) char, _Dest, __in size_t, _Size, __in_z __format_string const char *,_Format)

     

     

    この長ったらしいマクロはcrtdefs.hにて定義されています。

    Code Snippet
    #define __DEFINE_CPP_OVERLOAD_SECURE_FUNC_0_2_ARGLIST(_ReturnType, _FuncName, _VFuncName, _DstType, _Dst, _TType1, _TArg1, _TType2, _TArg2) \
        extern "C++" \
        { \
     __pragma(warning(push)); \
     __pragma(warning(disable: 4793)); \
        template <size_t _Size> \
        inline \
        _ReturnType __CRTDECL _FuncName(_DstType (&_Dst)[_Size], _TType1 _TArg1, _TType2 _TArg2, ...) \
        { \
            va_list _ArgList; \
            _crt_va_start(_ArgList, _TArg2); \
            return _VFuncName(_Dst, _Size, _TArg1, _TArg2, _ArgList); \
        } \
     __pragma(warning(pop)); \
        }

     

     

    色づけしてるとおり、_Sizeという名前がテンプレート引数と第2引数で重複しています。

    これにより、テンプレート引数の値は失われ、第2引数で指定した値で処理されます。

    従って、_TRUNCATEと指定した場合は4294967295の長さを持つバッファを与えられたものとして処理することとなり、スタック範囲外のメモリ書き込みで落ちます。

     

    回避策としては、テンプレートバージョンを使用しないこととなるでしょう。

    2008年4月23日 13:16
    モデレータ
  • 私のサンプルも MBCS で落ちました。こりゃダメだ。
    _snwprintf_s の方は _Size ではなく _Count になっているのでセーフということですね。

    2008年4月23日 15:41
  • MBCSの件について書くべきか迷って、同じ挙動であろうという思い込みから省略していました。
    申し訳ありません。

    私も今朝テストコードを作ってUNICODEとMBCSの両方で確認したところMBCSでのみエラーが発生しました。

    デバッグで追いかけたのですがおかしな値が出てくることまではわかったのですが原因まで追究できずにいたので、Azuleanさんの解説のおかげですっきりしました。

    結果的に_sntprintf_sのテンプレート引数は使ってはいけないということで、他に使っているところがないか探して修正します。
    アエトスさん、Azuleanさん、zakioさん、ありがとうございました。

    # でも、これってSP2とかで直してくれないんですかね?MSさん・・・
    2008年4月24日 1:15
  •  アエトス さんからの引用

    http://connect.microsoft.com/VisualStudioJapan/Feedback

    に報告をお願いできますか。

    日本語じゃない方のFeedbackに既に挙がっており、また「修正済み」と設定されていますね。

    Visual C++ 2008で修正されているので反映はされているのでしょう。

    (Visual C++ 2005のメンテで反映されるかは謎ですが)

     

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=99662

    2008年4月24日 13:39
    モデレータ