在没有 -DNDEBUG 和 -O3 的情况下编译时,标准库实现不使用断言有什么原因吗?

Is there any reason standard library implementations do not use asserts when compiling without -DNDEBUG and -O3?

无数次我编写的代码在访问其内存之外的 std::vectorstd::string 后生成 分段错误 :

std::string test{"hello!"};
std::cout << test[12] << std::endl;

这是一个可以在 non-optimized/debug 构建中的 运行 时间捕获的错误,只需简单断言的少量额外成本。 (但由于我们在没有 -DNDEBUG 且没有 -O3 的情况下进行构建,我们并不期望获得最佳性能。)

为什么 std::string::operator[] 不是这样实现的?

标准是否禁止在库代码中使用断言?

char std::string::operator[](std::size_t i)
{
    // `assert_with_message` only exists in debug mode

    #ifndef NDEBUG
        assert_with_message(i < this->size(),
            "Tried to access character " + std::to_string(i)
            + " from string '" + *this + "' of size " 
            + std::to_string(this->size()));
    #endif

    return data[i];
}

在没有 -DNDEBUG 的情况下编译程序并在 运行 时看到类似于此消息的内容将非常有帮助:

Assertion fired: Tried to access character 12 from string 'hello!' of size 6.

Press (0) to continue.

Press (1) to abort.

请注意,术语 assert 我指的是 development/debug 构建检查,它应该完全 removed/optimized-out 来自 release/optimized 构建.

你所做的是未定义的行为。由于此时允许进行任何操作,因此触发断言也是可以的。这是实现质量问题,看起来 libstdc++ 在这里不太好。

我认为为什么在标准构建中没有对所有案例进行断言的原因非常明显:它们是有代价的,并且试图访问不存在的索引处的内容是您代码中的错误, 而不是在标准库的代码中。

这可能是 C 和 C++ 以及大多数高级语言的不同之处:相对于 正确[=,期望的行为通常更 一致 29=]调用而不是容错

很多情况下不抛出异常是有原因的,而是 return 一些表明操作成功的东西(例如,假设你想使用字符串对象的查找方法 - - 出于性能原因,也因为 "not found" 听起来不太可能)。

首先,必须意识到抛出异常是一个非常复杂的过程,在运行时方面:您初始化相应异常的新对象,然后开始向上冒泡调用层次结构。通常,在发生灾难性故障的情况下(例如,程序员没有检查索引是否在范围内),这甚至可能使整个事情变得更糟(例如,尽管您可能假设 C++ 仅在 "proper" 具有大量内存的 PC 上运行, 还有微控制器执行用 c++ 编写的程序, 一个异常可能会吃掉所有内存)。

总而言之,C++ 没有你的支持。不是你的父亲教你骑自行车,如果你的计划开始变慢,他会轻轻地扶着你直立。而是有人允许你租一辆法拉利:他不会教你转弯时如何回头看,但他也不会评论你在高速公路上做 250 km/h 时的驾驶风格。开着那辆法拉利你可以做很了不起的事情,但是如果你自己不小心,你撞墙的速度会很惊人。

它的设计目的不是 Java(通过强迫您捕获不会发生的异常或非常灾难性的异常来不断妨碍您让您的软件崩溃) ,它也不能成为 python(不是强迫你做任何事情,而是作为一种脚本语言,其中生成异常对象的工作相对 normal/small 进行解析)。

C++ 希望您阅读文档,并谨慎使用或使用适当的方法。许多容器有不同的访问方法,有些有检查,有些没有。在许多情况下,您已经只是迭代其中的索引(例如,您执行类似 for(int i = 0; i < container.length(); i++) 的操作),因此您不必每次都检查(这只会浪费时间),在在某些情况下,您需要自己进行检查,在某些情况下,您可以使用 str.at(i),图书馆会进行检查。

标准库的多个实现确实在调试模式下提供了此类检查,但调试模式不受 NDEBUG 控制。对于 libstdc++,您需要 -D_GLIBCXX_DEBUG(参见 doc)。

有不同的标准库实现。其中一些可以(msvc 10 for one),有些则不能(gcc)。 不这样做的原因是它可能会极大地降低调试构建中的速度,以至于它不再真正可用。通常这样的实现仍然提供一些定义标志,所以你可以打开它(-D_GLIBCXX_DEBUG 用于 gcc)。另一方面,msvc 提供了 _ITERATOR_DEBUG_LEVEL 宏来在需要时将其关闭。