为啥创建静态 const std::string 会导致异常?

Posted

技术标签:

【中文标题】为啥创建静态 const std::string 会导致异常?【英文标题】:Why can creating a static const std::string cause an exception?为什么创建静态 const std::string 会导致异常? 【发布时间】:2016-11-02 10:24:17 【问题描述】:

我有字符串常量,用于在我的应用程序的多个位置使用的字符串:

namespace Common
    static const std::string mystring = "IamAwesum";

在发布有关其他问题的问题 (What happens to a .h file that is not included in a target during compilation?) 时,另一位用户发表了以下评论:

请注意,在这种情况下,您的静态字符串是全局的。所以他们是 可以随时创建异常并且无法捕获。我劝你 使用返回字符串引用的函数。标准::字符串 const &mystring 静态 std::string const mystring = "IamAwesum"; return mystring 通过这种方式,您的对象仅在需要时才构造

有人能解释一下为什么以我上面的方式使用静态 const 字符串会引发异常吗?

【问题讨论】:

在制作嵌入式应用程序的过程中,我遇到过类似的事情。动态内存分配可能还不起作用。 【参考方案1】:

N4140 § 3.6.2 [basic.start.init]/4

是否动态初始化是实现定义的 具有静态存储持续时间的非局部变量在 main 的第一条语句。

N4140 § N4140 15.3 [except.handle]/13

静态存储对象的析构函数中抛出的异常 持续时间或在具有静态的命名空间范围对象的构造函数中 main() 上的 function-try-block 未捕获存储持续时间。

您根本无法捕获由字符串的构造函数生成的异常 - 例如,std::bad_alloc

(观点)话虽如此,对于这么小的字符串,我觉得这种考虑是偏执的。

【讨论】:

问题是静态可以在您的任何代码“运行”之前由运行时初始化。这意味着,如果您的字符串足够长且内存足够小,您可能会遇到bad_alloc,而您自己的main 甚至没有运行一行代码,因此无法捕获这些异常。 @John,基本上有两种模型,其中初始化代码由编译器插入您的程序入口点。 (1) 编译器将它们注入到您的 main 函数定义中 (2) 编译器将您的 main 函数包装在另一个函数中,在那里进行初始化,然后调用您的 main。在第二种情况下,您无法在 main 函数中捕获异常,因为代码是在其上方的堆栈帧中执行的。 @John 阅读了意见部分。除非您正在为嵌入式开发,否则我发现您极不可能遇到这样的问题。 另外,@krzaq 即使会发生这种情况,我也想知道缺点是什么。这不像抓住bad_alloc 可以让你做你在没有抓住它时无法做的事情,因为此时你不能保证任何事情。 当然,在某些嵌入式系统上,所有代码都已经在 ROM 中,预先分配堆栈的进程和线程数量有限,可修改静态数据区域的大小受到限制,所有这些,这可能意味着程序启动是有保证的,而且std::terminate() 的后果可能比你通过捕获异常所能达到的更糟糕。然后确定,您要确保捕获异常。如果您正在编写一个 Windows 应用程序,那么当 10 字节分配失败时,系统已经很糟糕了,您所做的任何事情都可能无济于事......【参考方案2】:

唯一的“问题”——如果你可以这样称呼它——我在你的代码中看到的是,你通过将已经是常量的数据不必要地复制到动态分配的缓冲区(正式地 不变,但实际上并非如此)。这会使用两倍于必要的物理内存并进行不必要的复制。

这有关系吗?几乎可以肯定,没有。即使在“内存相当有限”的系统上,现在也很难注意到这一点,无论是从执行时间的角度来看,还是从内存消耗的角度来看。

至于例外,当然技术上确实是std::string必须使分配可能失败,因此构造函数可能 em> 扔,你将无法抓住它。但请现实一点。 这几乎可以保证不会发生,但即使它确实发生了......如果在你的程序启动时为几个字符串分配内存这样微不足道的事情失败了,你就会遇到一个完全不同规模的非常非常严重的问题! 此外,正如在上面对另一个答案的评论中指出的那样:假设确实发生了这种情况,您将如何处理?该程序完全无法运行,因此您可以想象到要杀死该程序。

现在,由于 C++17 已经不远了,string_view 已经在几个主流编译器的std::experimental 中可用,您可以尝试另一件事:使用正确的东西

string_viewstring 不同,不会分配非常量内存,将常量数据复制到其中,然后假装它是常量。相反,它将直接管理指向常量数据的指针,仅此而已。 这样,您的常量是真正的(不仅仅是形式上的)常量,没有分配,没有异常的可能性,也没有双重内存使用。在大多数情况下,它看起来和闻起来都像string。唯一显着的区别是 string_view 不保证 nul 终止(但它指向的字符常量确实如此,所以这无关紧要),而且它是 真的 常量,不可修改的事实...这正是您想要的。

【讨论】:

使用strings 可能是正确的做法™,如果他将它们传递给许多通过 const ref 获取字符串的函数。不过,总的来说,我同意你的看法。 请注意,可能不再经常需要 std::string 。我正在为我的许多函数转换到 string_view(如果我没记错的话),目前依赖于 boost::string_ref。除非你需要一个以 0 结尾的字符串,否则 string_view 比 std::string const&.. 有很多优势。 还有其他选择吗?有没有办法初始化一个全局字符串变量?【参考方案3】:

pdf 文档主要是指来自对象 ctor 的异常以及使用静态或动态链接库的初始化顺序惨败。

我在您的代码中看到的唯一危险是 std::string 的 ctor 在被调用时是否会抛出。

如果您真的想安全起见,可以使用 static const char* mystring 代替,它不会调用 C++ ctor。

还有代码在共享库中的问题,然后需要将其放置在进程的地址空间中。 如果您不使用复杂的 ctors(可以抛出的 ctors),我不认为这是一个主要问题。

【讨论】:

不能创建 const char* 导致您也无法响应的分配错误? 如果您使用 new 或 malloc 则可以。如果您使用字符串文字,编译器会将字符串存储在可执行文件中,并且不会涉及任何 C++ 构造和销毁。 如果可用内存太少,应用程序甚至无法启动? 不,应用程序将启动。这就是虚拟内存发挥作用的地方。操作系统让应用程序认为它拥有所有机器内存,但实际上它会将部分内存交换到磁盘。当应用程序尝试访问该内存时,操作系统会检测到该内存并将所需的内存数据加载回内存。

以上是关于为啥创建静态 const std::string 会导致异常?的主要内容,如果未能解决你的问题,请参考以下文章

std::string 生成链接器错误—— const char* 不会。为啥?

为啥 std::string_view 比 const char* 快?

为啥 g++ 不关心初始化列表分配给 (const std::string&) a (std::string)?和其他怪异[关闭]

为啥我可以在 std::map<std::string, int> 中使用 const char* 作为键

为啥编译器更喜欢 f(const void*) 而不是 f(const std::string &)?

c++,为啥要使用 const std::string & parameterName? [复制]