C++ std::string alternative to strcpy?

C++ std::string alternative to strcpy?

本文关键字:strcpy to string std C++ alternative      更新时间:2023-10-16

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

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

1>c:somethingmycontrol.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();
...

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

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

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

  1. *_s函数是不可移植的Microsoft发明几乎都复制了其他可移植的C库函数的功能。 永远不应该使用它们,即使在不打算可移植的程序中(这似乎是这样(。
  2. 盲目更改往往不能真正修复此类错误。 例如,strcpy(strncpystrlcpystrcpy_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''; // MultiByteToWideChar does not NUL-terminate.
    this->font_.DeleteObject();
    if (!this->font_.CreateFontIndirectW(&lf))
        throw GetLastError(); // FIXME: use a real exception
    // ...
}

lf.lfFaceName = faceName.c_str();

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

由于您需要复制 c 字符串,因此您需要一个"c"函数,那么strcpy_s(或等效(是安全的替代方案

你试过吗?鉴于您帖子中的信息,赋值应该会生成编译器错误,因为您正在尝试分配指向数组的指针,这在 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 替代方案,如警告所说,因为您无论如何都知道目标空间的大小。

#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;
}

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

  1. c_str(( 返回的指针的生存期是暂时的。 仅当 fileName 对象不更改且处于活动状态时,它才有效。
  2. .c_str(( 返回指向字符的指针,而 lfFaceName 是一个字符数组,不能分配给。 你需要做一些事情来填充字符串数组,在lfFaceName上填充字节,而指针赋值不会这样做。

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

strcpy_s(lf.lfFaceName, LF_FACESIZE, faceName);