向 const 字符串参数发送 0 int 文本时的访问冲突

Access Violation when sending a 0 int literal to a const string parameter

本文关键字:文本 int 访问冲突 const 字符串 参数      更新时间:2023-10-16

在VS2015和VS2017上,编译时没有警告,并生成无法捕获的访问冲突并使应用程序崩溃。显然,int 0 被静默地转换为空指针,然后假定该指针指向一个字符串,因此崩溃。

#include <string>
#include <iostream>
void crash(const std::string& s) {}
int main()
{
try
{
crash(0);
}
catch (const std::exception& ex)
{
// never gets here!
std::cout << "got" << ex.what() << std::endl;
}
}

如何捕获此类异常并从中恢复? 如果我从函数参数中删除 const,它不会编译 - 所以这可能是防止用户滥用的一种方式,但我会失去 const 提供的保护,还是会? 编写避免此问题的原型的最佳实践是什么?

对于这种特定情况,您可以使用 C++11std::nullptr_t获得编译时错误,只需添加以下已删除的重载:

void crash(std::nullptr_t) = delete;

当然,这不会保护您传递空(或非以空结尾)字符*指针...您违反了 std::string 构造函数前提条件,导致未定义的行为;根据定义,这是不可恢复的。

或者,如果您确实需要在运行时以可能可恢复的方式捕获这些错误,则可以编写一个const char*重载,如果给定 null 指针,则会引发该重载,否则会调用std::string const&版本。

如果你的实函数需要多个字符串参数,并且重载所有可能的组合似乎不可行,你可以重新编写一个函数模板,在事后对推导的类型执行所有检查。

namespace safer {
template<class CharT,
class Traits = ::std::char_traits<CharT>,
class Allocator = ::std::allocator<CharT>,
class Base = ::std::basic_string<CharT, Traits, Allocator>
>
struct basic_string:
Base
{
using Base::Base;
basic_string( CharT const* ptr ):
Base( ptr?Base(ptr):Base() )
{}
};
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
}

safer::stringstd::string基本相同,但在从空指针构造时不会崩溃。 相反,它将其视为空字符串。

只需从代码库中清除所有提及std::string的内容,并替换为safer::string,以及类似的std::wstringstd::basic_string

void crash(const safer::string& s) {}

您可以选择抛出而不是默默地消耗值。

我们也可以在编译时检测0

namespace safer {
template<class CharT,
class Traits = ::std::char_traits<CharT>,
class Allocator = ::std::allocator<CharT>,
class Base = ::std::basic_string<CharT, Traits, Allocator>
>
struct basic_string:
Base
{
using Base::Base;
basic_string( CharT const* ptr ):
Base( ptr?Base(ptr):Base() )
{}
template<class T,
// SFINAE only accepts prvalues of type int:
std::enable_if_t<std::is_same<T, int>::value, bool> = true
>
basic_string(T&&)=delete; // block 0
};
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
}

现在传递0会得到一个编译时错误,传递nullptr或空char const*会得到一个空字符串。

活生生的例子。


所以,有些人对我告诉你从std中的非多态类型继承的事实感到紧张。 有一些理由不从非多态类型继承,但在这里都不适用。

但是,一般来说,从不是为多态性设计的类型(如std::basic_string<CharT>)继承时要小心。 在这种特殊情况下,将safer::basic_string<T>存储在std::basic_string<T>*中,然后对其调用delete是未定义的行为(或在调用deletestd::unique_ptr<std::basic_string<T>>中)。 但是动态分配basic_string通常首先是一个错误,因此不太可能发生。

此外,此继承必须遵守 LSP,而不会更改基类的任何方法的行为。 在这种情况下,我们正在调整结构,并且构造永远不会是多态的。 如果我们有任何不是构造的写入操作,我们希望在降序类中保持不变,我们将遇到麻烦。