我知道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
来修复该错误,因为:
*_s
函数是不可移植的Microsoft发明几乎都复制了其他可移植的C库函数的功能。 永远不应该使用它们,即使在不打算可移植的程序中(这似乎是这样(。- 盲目更改往往不能真正修复此类错误。 例如,
strcpy
(strncpy
、strlcpy
、strcpy_s
(的"安全"变体如果字符串太长,就会截断字符串,在这种情况下,这会让你尝试加载错误的字体。 更糟糕的是,strncpy
这样做时省略了 NUL 终结器,因此如果您使用该终止符,您可能只会将崩溃移动到CreateFontIndirect
内。 正确的解决方法是预先检查长度,如果太长,则整个操作失败。 在这一点上,strcpy
变得安全(因为你知道它不会太长(,尽管我更喜欢memcpy
因为它让未来的代码读者清楚地知道我已经想到了这一点。 -
TCHAR
和char
不是一回事;在没有正确编码转换的情况下,将 C 样式的const char *
字符串或C++std::string
复制到TCHAR
数组中可能会产生完全无稽之谈。 (根据我的经验,使用TCHAR
总是一个错误,它最大的问题是这样的代码在 ASCII 构建中看起来可以正常工作,并且仍然可以在 UNICODE 模式下编译,但随后会在运行时灾难性地失败。
您当然可以使用std::string
来帮助解决此问题,但它不会让您摆脱检查长度和手动复制字符串的需要。我可能会这样做。 请注意,我正在使用LOGFONTW
和CreateFontIndirectW
,并在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(
- c_str(( 返回的指针的生存期是暂时的。 仅当 fileName 对象不更改且处于活动状态时,它才有效。
- .c_str(( 返回指向字符的指针,而 lfFaceName 是一个字符数组,不能分配给。 你需要做一些事情来填充字符串数组,在lfFaceName上填充字节,而指针赋值不会这样做。
这里没有任何C++可以帮助,因为 lfFaceName 是一个 C"字符串"。 你需要使用 C 字符串函数,如 strcpy 或 strcpy_s。 您可以将代码更改为:
strcpy_s(lf.lfFaceName, LF_FACESIZE, faceName);