【问题标题】:How sprintf works with CString and std::stringsprintf 如何与 CString 和 std::string 一起工作
【发布时间】:2014-03-29 00:18:27
【问题描述】:
CString s = "test";
std::string ss = "test";

char z[100];
sprintf(z, "%s", ss.c_str()); // z = "test"  : OK

char z2[100];
sprintf(z2, "%s", ss); // z2 = "(null)" : OK. undefined behavior is expected

char z3[100];
sprintf(z3, "%s", s); // z3 = "test" : How is this possible ?!

谁能解释CString如何与sprintf一起正常工作?

【问题讨论】:

    标签: c++ visual-c++ visual-studio-2008 mfc cstring


    【解决方案1】:

    之所以有效,是因为 CString 类的第一个元素是指向 char 数组的指针。实际上,CString 中唯一的字段是指向字符串数组的指针。此类使用一些技巧来隐藏内部数据(如字符串长度、保留的缓冲区大小等),方法是分配一个大缓冲区,然后将唯一的类指针指向 char 数组,以获取那些内部数据字段,它将这个指针移动已知偏移量。

    你应该做的是调用 s.GetBuffer(0);或 (LPCTSTR)s;但将其用作

    sprintf(z2, "%s", ss);
    

    MFC 创建者的设计是允许的,当然它可以在其他平台上的 Windows 下运行,它可能会崩溃。

    [在 cmets 之后编辑]

    如果您将使用 c++ cast:static_cast<LPCTSTR>(s); 而不是像 (LPCTSTR)s 这样的 c 样式转换,您的代码会更安全。但是很快你就会发现你的代码在使用所有这些 static_cast-s 时变得很难看,特别是如果你的 sprintf-s 有很多参数。据我记得(在我看来)这是设计上的,c++ 风格的转换旨在让您重新考虑您的设计,以完全不使用转换。在你的情况下,而不是使用 sprintf 你应该使用 std::wstringstream (假设你使用 UNICODE 构建):

    #include<sstream>
    
    std::wostream & operator<< (std::wostream &out, CString const &s) {
      out << s.GetString();
      return out;
    }
    
    int main(){
      CString s = _T("test");
      std::wstringstream ss;
      ss << s;  // no cast required, no UB here
      std::wcout << ss.str();
      return 0;
    }
    

    【讨论】:

    • 正确的调用方法是CString::GetString()不是GetBuffer()。另请注意,C 风格的转换很糟糕:请使用 C++ 风格的转换static_cast&lt;LPCTSTR&gt;(ss).
    • 更准确地说,在您的情况下,演员表应该是static_cast&lt;const char*&gt;(ss),因为您使用的是sprintf()(不是基于TCHAR 的函数,如_stprintf_s())。这样,在 Unicode 构建(自 VS2005 以来一直是默认值)上,CString 实际上是 CStringW,转换失败并且代码无法编译,您可以修复它(而不是传递错误的参数到sprintf(),并在运行时默默地引入一个错误)。
    • ss 是示例中的std::string,ITYM s.GetBuffer(0) 等。否则它将是ss.c_str()
    • 感谢 cmets,我已将它们包含在我的编辑中。我不确定OP是否使用UNICODE,据我所知,CString可以在分配给它时将多字节隐式转换为wchar_t字符串。
    【解决方案2】:

    CString 的这种行为似乎没有微软官方支持(它依赖于CString实现细节,它似乎可以在类似的情况下工作您引用了,但将来可能会改变)。

    请注意,MSDN documentation of CString PCXSTR cast operator 读作:

    // If the prototype isn't known or is a va_arg prototype, 
    // you must invoke the cast operator explicitly. For example, 
    // the va_arg part of a call to swprintf_s() needs the cast:
    
    swprintf_s(sz, 1024, L"I think that %s!\n", (PCWSTR)strSports);
    

    实际上这种演员阵容很糟糕,因为它是 C 风格的演员阵容。我会使用 C++ 风格的转换 static_cast&lt;PCWSTR&gt;(string) 或只是 CString::GetString() 方法。

    另一个MSDN documentation page 写着(强调我的):

    在可变参数函数中使用 CString 对象

    一些 C 函数采用可变数量的参数。一个值得注意的 例如printf_s。因为这种功能的方式 声明时,编译器无法确定参数的类型和 无法确定对每个执行哪个转换操作 争论。因此,您必须在传递时使用 显式类型转换 一个 CString 对象到一个函数,该函数接受一个可变数量的 论据。要在可变参数函数中使用 CString 对象, 将CString 显式转换为LPCTSTR 字符串,如 下面的例子。

    CString kindOfFruit = _T("bananas");
    int howmany = 25;
    _tprintf_s(_T("You have %d %s\n"), howmany, (LPCTSTR)kindOfFruit);    
    

    再一次,我确实更喜欢 C++ 风格 static_cast&lt;PCTSTR&gt;,而不是 MSDN 文档中使用的 C 风格转换。或者拨打CString::GetString() 就可以了。


    另请参阅此博文:Big Brother helps you

    还有一个thread on StackOverflow in which this issue is discussed

    【讨论】:

    • 感谢关于演员阵容的详细解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-22
    • 1970-01-01
    • 1970-01-01
    • 2016-08-22
    相关资源
    最近更新 更多