【问题标题】:Why does iostream require the exception handler to be called while using vcvarsall.bat to compile 'Hello World'?为什么 iostream 需要在使用 vcvarsall.bat 编译“Hello World”时调用异常处理程序?
【发布时间】:2021-04-23 22:33:36
【问题描述】:

尝试在命令行中使用 vcvarsall.bat 编译以下代码会引发警告,指出代码中需要异常处理程序,但在使用 /EHsc 之前未调用。

代码:

#include <iostream>

int main()
{
    std::cout << "hello world" << std::endl;
    
    return 0;
}

批处理文件:

@echo off

cl C:\Development\..\basicmath.cpp

警告:

C:\...\ostream(746): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
C:...\basicmath.cpp(10): note: see reference to function template instantiation 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)' being compiled

ostream 第 746 行的第 743 - 754 行(来自错误)是 _TRY:

if (!_Ok) {
        _State |= ios_base::badbit;
    } else { // state okay, insert
        _TRY_IO_BEGIN
        if ((_Ostr.flags() & ios_base::adjustfield) != ios_base::left) {
            for (; 0 < _Pad; --_Pad) { // pad on left
                if (_Traits::eq_int_type(_Traits::eof(), _Ostr.rdbuf()->sputc(_Ostr.fill()))) {
                    _State |= ios_base::badbit; // insertion failed, quit
                    break;
                }
            }
        }

将 /EHsc 添加到我的批处理文件将允许它运行,但我想知道这是为什么。 为什么输出文件中的这段代码需要调用 EHsc?

MSDOCS 说 EHsc 用于清理以防止内存泄漏,是什么导致了泄漏,为什么他们需要外部程序来修复泄漏而不是在同一个文件中修复它(这听起来可能很粗鲁,但它只是无知)?

编辑:感谢您指出它是警告而不是错误。

【问题讨论】:

  • 注意:这不是错误,而是警告。它不会阻止编译成功,除非您有 /WX 选项(将警告视为错误)。我必须说,如果编译器的开发人员首先将行为 A 定义为默认行为,然后建议将其更改为行为 B,这似乎是一个奇怪的决定,这样会更好。我想这一定是有原因的。一个 TL;DR 的答案真的是——在所有项目的编译选项中添加 /EHs(或 /EHsc),你就可以忘记这个话题了。
  • 感谢您指出这是一个警告而不是错误,我什至没有注意到它仍然可以很好地创建我的.exe 并且运行没有问题。如果没有成功,我会非常专注。
  • 默认情况下 cl 不会为标准 C++ 编译。它编译为支持(除其他外)SEH(Microsoft 特有的结构化异常处理方法)和堆栈展开(异常在调用堆栈上传播时发生的情况)的特定于 Microsoft 的方言。 &lt;iostream&gt; 中的某些函数会引发 C++ 异常。 /EHsc 中的 s 启用与 C++ 标准要求一致的堆栈展开。 c 指示编译器假定 extern "C" 函数不会抛出。当 IDE 在后台使用 cl 编译 C++ 时,通常默认启用这些选项。
  • 有趣,如果我理解,我可能不会,cl 是 vcvarsall.bat 的一部分,所以如果我使用不同的编译器,它不会使用 cl 并且默认情况下会有 eh?
  • @BornGeek - 据我了解,默认情况下支持 SEH - 如果您的代码仅使用 SEH 而没有 C++ 异常,它将起作用。它与需要启用的标准 C++ 的要求一致的堆栈展开(假设您有可能引发 C++ 异常的代码 - &lt;iostream&gt; 就是这种情况 - iostreams 默认情况下不会引发异常,但改变它是可能在运行时)。

标签: c++ exception compiler-errors


【解决方案1】:

简答:

按照the documentation 的建议,将/EHs/EHsc 添加到您的编译选项中。如果您需要在 Unix 机器上执行相同的代码,它是关于异常处理的最便携的选项。


长答案:

这个问题有两个部分。一是为什么会在iostream出现警告,二是警告是什么意思。

为什么iostream会有例外?

C++ 中流的默认行为是无异常的——任何失败都通过设置一个内部失败位来表示,可以通过eof()fail()bad() 函数访问。但是,您可以通过在流上使用exceptions() 方法将此行为更改为在失败时引发异常。您可以选择哪些失败位触发异常,但要点是代码必须按标准存在。警告似乎只分析了这一点 - 它注意到throw 出现的可能路径并报告警告。

警告是什么意思?

来自Microsoft documentation(强调我的):

默认情况下(即,如果未指定 /EHsc/EHs/EHa 选项),编译器在本机 C++ catch(...) 子句中支持 SEH 处理程序。 但是,它也会生成仅部分支持 C++ 异常的代码。默认的异常展开代码不会破坏由于异常而超出范围的 try 块之外的自动 C++ 对象。

问题是(出于某种原因)MSVC 编译器默认生成的程序集根据标准是错误的。抛出异常时不会执行堆栈展开,这可能会导致内存泄漏和其他意外行为。

一个正确的 C++ 代码示例,在默认设置下存在内存泄漏:

void foo()
{
    std::string str = "This is a very long string. It definitely doesn't use Small String Optimization and it must be allocated on the heap."
    std::cout << str;
    throw std::runtime_error{"Oh no, something went wrong"};
}

int main()
{
    try
    {
        foo();
    }
    catch (std::exception&)
    {
        // str in foo() was possibly not released, because it wasn't deleted when exception was thrown!
    }
}

所以最终的答案是:

  • 如果您打算使用Structured Exceptions(例如被零除或无效内存访问错误)或使用使用它们的库,请使用/EHa
  • 如果您不需要捕获 SE,请选择 /EHs 以兼容 C++ 标准和可移植性
  • 永远不要保留默认值,始终将/EH 设置为一种或另一种,否则在使用异常时您将不得不处理奇怪的行为。

【讨论】:

  • 感谢您的回答。另一位用户@peter 提到“默认情况下 cl 不会为标准 C++ 编译。它编译为支持(除其他外)SEH(一种微软特有的结构化异常处理方法)和堆栈展开(当异常传播到调用堆栈时会发生什么)的特定于微软的方言。”有没有其他编译器不会出现这个问题?
  • 虽然 iostreams 默认不抛出异常,但可以在运行时更改。因此,编译器有义务检查是否存在任何可能引发异常的执行路径。由于&lt;iostream&gt; 类是模板化的,因此编译器通常可以看到那些潜在的执行路径,但不知道其他代码(在其他编译单元中)是否也使用 C++ 流,并在某些流上启用异常处理。跨度>
【解决方案2】:

这是一个警告,因此您当前的程序可以正常编译。但是这样的程序会出现问题:

#include <exception>
#include <iostream>

struct A{
    A(int x):x(x) {
        std::cout<<"Contructed A::"<<x<<'\n';
    }
    ~A() {
        std::cout<<"Destructed A::"<<x<<'\n';
    }
private:
    int x;
};


void foo() {
    A a{2};
    throw std::bad_exception{};
}

int main()
{
    A a {1};
    try {
        foo();
    } catch(const std::bad_exception& ex) {
        std::cout<<ex.what()<<'\n';
    }
    
    return 0;
}

使用cl test.cpp 产生输出:

Contructed A::1
Contructed A::2
bad exception
Destructed A::1

使用cl test.cpp /EHsc 时:

Contructed A::1
Contructed A::2
Destructed A::2
bad exception
Destructed A::1

警告C4530 的文档解释了此行为:

当 /EHsc 选项未启用时,自动存储对象 在抛出函数和函数所在的函数之间堆栈帧 异常被捕获不要被破坏。只有自动存储 在 try 或 catch 块中创建的对象被破坏,这可能导致 严重的资源泄漏和其他意外行为。

这解释了当程序未使用 /EHsc 编译时,a {2} 不会被破坏。

当然,

如果在你的可执行文件中不可能抛出异常,你可以 请放心忽略此警告。

所以,对于像这样的程序

#include <cstdio>

int main()
{
    std::printf("hello world\n");
    
    return 0;
}

cl.exe 悄悄编译。

【讨论】:

  • 谢谢,这与我没有经验的尝试寻找其他可以在没有警告的情况下工作的库是一致的。即使使用其他库,问题仍然存在?
  • @BornGeek 任何涉及异常处理的库都会有同样的问题。我认为这只是微软编译器的一个限制。 g++/clang没有这个问题。
猜你喜欢
  • 2012-10-24
  • 1970-01-01
  • 2014-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-05
  • 1970-01-01
相关资源
最近更新 更多