为什么 iostream 在使用 vcvarsall.bat 编译 'Hello World' 时需要调用异常处理程序?

Why does iostream require the exception handler to be called while using vcvarsall.bat to compile 'Hello World'?

尝试在命令行中使用 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 是为了清理以防止内存泄漏,是什么导致了泄漏,为什么他们需要一个外部程序来修复泄漏而不是在同一个文件中修复它(这听起来很粗鲁,但它只是无知)?

编辑:感谢您指出这是警告而非错误。

简答:

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


长答案:

这个问题有两个部分。首先是警告出现在 iostream 中的原因,其次是警告的含义。

为什么iostream有异常?

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

警告是什么意思?

来自 Microsoft documentation(强调我的):

By default (that is, if no /EHsc, /EHs, or /EHa option is specified), the compiler supports SEH handlers in the native C++ catch(...) clause. However, it also generates code that only partially supports C++ exceptions . The default exception unwinding code doesn't destroy automatic C++ objects outside of try blocks that go out of scope because of an exception.

问题是(出于某种原因)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 设置为一种或另一种选择,否则在使用异常时您将不得不处理奇怪的行为。

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

#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:

When the /EHsc option isn't enabled, automatic storage objects in the stack frames between the throwing function and the function where the exception is caught don't get destroyed. Only the automatic storage objects created in a try or catch block get destroyed, which can lead to significant resource leaks and other unexpected behavior.

这解释了当程序不是用 /EHsc 编译时 a {2} 没有被破坏。

当然,

If no exceptions can possibly be thrown in your executable, you may safely ignore this warning.

所以,对于像

这样的程序
#include <cstdio>

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

cl.exe悄悄编译。