【问题标题】:C++ std::string alternative to strcpy?C++ std::string 替代 strcpy?
【发布时间】:2011-12-12 17:21:00
【问题描述】:

我知道 SO 上已经有一个类似标题的问题,但我想知道我对这个特定案例的选择。

MSVC 编译器给出关于 strcpy 的警告:

1>c:\something\mycontrol.cpp(65): warning C4996: 'strcpy': This function or
variable may be unsafe. Consider using strcpy_s instead. To disable
deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

这是我的代码:

void MyControl::SetFontFace(const char *faceName)
{
    LOGFONT lf;

    CFont *currentFont = GetFont();
    currentFont->GetLogFont(&lf);
    strcpy(lf.lfFaceName, faceName); <--- offending line
    font_.DeleteObject();
    // Create the font.
    font_.CreateFontIndirect(&lf);

    // Use the font to paint a control.
    SetFont(&font_);
}

注意font_ 是一个实例变量。 LOGFONT 是一个窗口结构,其中lfFaceName 定义为TCHAR lfFaceName[LF_FACESIZE]

我想知道的是我可以做如下的事情(如果不是为什么不这样做):

void MyControl::SetFontFace(const std::string& faceName)
...
  lf.lfFaceName = faceName.c_str();
...

或者如果有完全不同的选择,请告诉我。

【问题讨论】:

  • 你可以忽略这个警告,strcpy 没有被弃用。在您的设置中永久定义_CRT_SECURE_NO_WARNINGS 并完成它。
  • strcpy 的危险在于,如果源字符串不是以 NULL 结尾的,或者比目标缓冲区长,您将得到缓冲区溢出 - 这是最常见的安全漏洞来源之一C/C++ 代码。 strcpy_s 还获取目标缓冲区的大小,并保证成功完成后,目标缓冲区将空终止。
  • strncpy 更便携,同样安全。
  • @MooingDuck,strncpy 的问题在于它被破坏了:如果它必须截断它不会空终止。所以最好使用一个总是在最后添加零的快速包装器。

标签: c++ stdstring strcpy


【解决方案1】:

您收到安全警告的原因是,您的 faceName 参数可能指向一个LF_FACESIZE 字符长 的字符串,然后strcpy 会盲目地覆盖任何出现的内容在LOGFONT 结构中的lfFaceName 之后。你确实有错误。

您应该通过将strcpy 更改为strcpy_s 来盲目修复错误,因为:

  1. *_s 函数是不可移植的 Microsoft 发明,几乎所有这些函数都复制了其他可移植的 C 库函数的功能。永远不要使用它们,即使是在一个不打算移植的程序中(看起来就是这样)。
  2. 盲目的更改往往无法真正修复此类错误。例如,strcpystrncpystrlcpystrcpy_s)的“安全”变体在字符串太长时会简单地截断字符串,在这种情况下会导致您尝试加载错误的字体。更糟糕的是,strncpy 在执行此操作时会省略 NUL 终止符,因此如果您使用该终止符,您可能只需将崩溃移入 CreateFontIndirect正确 修复方法是预先检查长度,如果太长则使整个操作失败。此时strcpy 变得安全(因为你知道它不会太长),虽然我更喜欢memcpy,因为它让未来的代码读者明白我已经考虑过这一点。
  3. TCHARchar 不是一回事;将 C 风格的 const char * 字符串或 C++ std::string 复制到 TCHAR 的数组中而没有正确的编码转换可能会产生完全的废话。 (根据我的经验,使用TCHAR 总是一个错误,最大的问题是这样的代码在 ASCII 构建中似乎可以正常工作,并且仍然可以在 UNICODE 模式下编译 ,但会在运行时发生灾难性的失败。)

你当然可以使用std::string帮助解决这个问题,但它不会让你不需要检查长度并手动复制字符串。我可能会这样做。请注意,我正在使用LOGFONTWCreateFontIndirectW 以及std::string 中从UTF-8 的显式转换。另请注意,其中的大部分内容是从 MSDN 中提取出来的,并且没有经过测试。对不起。

void MyControl::SetFontFace(const std::string& faceName)
{
    LOGFONTW lf;
    this->font_.GetLogFontW(&lf);

    int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
                                    faceName.data(), faceName.length(),
                                    lf.lfFaceName, LF_FACESIZE - 1)
    if (count <= 0)
        throw GetLastError(); // FIXME: use a real exception

    lf.lfFaceName[count] = L'\0'; // MultiByteToWideChar does not NUL-terminate.

    this->font_.DeleteObject();
    if (!this->font_.CreateFontIndirectW(&lf))
        throw GetLastError(); // FIXME: use a real exception

    // ...
}

【讨论】:

  • strcpy_s 保证空终止,它不是 strcpyn 的重复,这就是为什么它是一个不同的函数
  • @shf301:是的,但它盲目地导致截断相同的问题。因此这里的建议是合理的。
  • @shf301 我不知道;这使它成为 strlcpy 而不是 strncpy 的副本。 (唉,strlcpy 本身就是一个 BSD 主义,但 MS 真的可以明白,为事物编造自己的名字不是是件好事。)
  • 我深思熟虑后认为_s 函数不应该被使用即使在不打算移植的代码中,因为编造它们的人应该受到惩罚因为他们没有检查他们是否在复制现有的 API,并且拒绝使用它们是唯一可用的。
  • 从技术上讲,他们并没有复制现有的 API,因为他们添加了参数验证错误返回值,并且缓冲区大小参数始终以字节表示(而长度以标准字符宽度表示,即如果您尝试编写 _UNICODE 不可知的代码,这是一个重要的区别)。
【解决方案2】:

lf.lfFaceName = faceName.c_str();

不,您不应该这样做,因为您正在为 std::string 中保存的数据制作指针的本地副本。如果 c++ 字符串更改或被删除,则指针不再有效,并且如果 lFaceName 决定更改数据,这几乎肯定会破坏 std::string。

既然你需要复制一个 c 字符串,你就需要一个 'c' 函数,那么 strcpy_s(或它的等价物)是安全的替代方案

【讨论】:

  • 如果 lf.lfFaceName 是一个数组,我认为它不会这样工作。
【解决方案3】:

你试过了吗?鉴于您帖子中的信息,分配应该会产生编译器错误,因为您正在尝试分配指向数组的指针,这在 C(++) 中不起作用。

#include <cstdio>
#include <string>
using namespace std;

struct LOGFONT {
 char lfFaceName[3];
};


int main() {
        struct LOGFONT f;
        string foo="bar";
        f.lfFaceName = foo.c_str();
        return 0;
}

导致

x.c:13: error: incompatible types in assignment of `const char*' to `char[3]'

如果您知道目标空间的大小,我建议您使用安全的 strcpy 替代方案,如警告所述。

【讨论】:

    【解决方案4】:
    #include <algorithm>
    #include <iostream>
    #include <string>
    
    enum { LF_FACESIZE = 256 }; // = 3 // test too-long input
    struct LOGFONT
    {
        char lfFaceName[LF_FACESIZE];
    };
    
    int main()
    {
        LOGFONT f;
        std::string foo("Sans-Serif");
        std::copy_n(foo.c_str(), foo.size()+1 > LF_FACESIZE ? LF_FACESIZE : foo.size()+1,
                    f.lfFaceName);
    
        std::cout << f.lfFaceName << std::endl;
        return 0;
    }
    

    【讨论】:

      【解决方案5】:

      lf.lfFaceName = faceName.c_str(); 不起作用有两个原因(假设您将 faceName 更改为 std:string)

      1. c_str() 返回的指针的生命周期是临时的。它仅在 fileName 对象不更改且处于活动状态时才有效。
      2. 该行无法编译。 .c_str() 返回一个指向 char 的指针,lfFaceName 是一个字符数组,不能赋值。您需要做一些事情来填充字符串数组,以填充 lfFaceName 处的字节,而指针分配不会这样做。

      这里没有任何 C++ 可以提​​供帮助,因为 lfFaceName 是一个 C“字符串”。您需要使用 C 字符串函数,例如 strcpy 或 strcpy_s。您可以将代码更改为:

      strcpy_s(lf.lfFaceName, LF_FACESIZE, faceName);
      

      【讨论】:

      • 我想你的意思是写strncpy,而不是strcpy。此外,您的示例代码中存在语法错误。
      • 不,你的意思是strncpy。正如我在回答中解释的那样,永远不应该使用strcpy_s。此外,您还没有修复语法错误。
      猜你喜欢
      • 1970-01-01
      • 2011-06-12
      • 1970-01-01
      • 2011-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-08
      相关资源
      最近更新 更多