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

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

本文关键字:断言 实现 什么 -O3 -DNDEBUG 情况下 编译 标准      更新时间:2023-10-16

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

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

这是一个错误,可能会在非优化/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的情况下编译程序,并在运行时看到类似于以下消息的内容,那将非常有帮助:

断言已激发:尝试访问字符串"hello!"中的字符12属于尺寸6。

按(0)继续。

按(1)中止。

请注意,术语assert指的是开发/调试构建检查,它应该从发布/优化的构建中完全删除/优化。

标准库的一些实现确实在调试模式下提供了这样的检查,但调试模式不受NDEBUG控制。对于libstdc++,您需要-D_GLIBCXX_DEBUG(请参阅文档)。

您所做的是未定义的行为。由于在这一点上任何事情都是允许的,所以触发断言也是可以的。这是实现质量的问题,看起来libstdc++在这里不太好。

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

我认为标准构建中没有对任何情况进行断言的原因非常明显:它们是有代价的,试图访问不存在的索引是代码中的一个错误,而不是标准库的代码中的错误。

这可能是C和C++以及大多数高级语言的不同之处:对于正确的调用,所需的行为通常更为一致,而不是容错。

很多情况下都有理由不抛出异常,而是返回一些指示操作成功的信息(例如,假设您想使用字符串对象的find方法——出于性能原因,也因为"未找到"听起来不像是不太可能的)。

最重要的是,必须意识到抛出异常在运行时是一个非常复杂的过程:初始化相应异常的新对象,然后开始冒泡调用层次结构。通常,在灾难性故障的情况下(例如,程序员没有检查索引是否在范围内),这甚至可能会使整个情况变得更糟(例如,尽管你可能认为C++只在有大量内存的"适当"PC上运行,但也有微控制器在执行用C++编写的程序,一个异常可能会占用所有内存)。

总而言之,C++只是不支持你。这不是你的父亲,教你如何骑自行车,如果你的程序开始出错,他会轻轻地把你扶起来。这是一个允许你租法拉利的人:他不会教你转弯时如何回头看,但他也不会评论你在高速公路上以250公里/小时的速度行驶时的驾驶风格。你可以用那辆法拉利做一些很棒的事情,但如果你不小心,当你撞到墙上时,你会有一个很棒的速度。

它不是为Java设计的(不断地通过强迫你捕捉不会发生或灾难性的异常来阻碍你),也不能成为python(不强迫你做任何事情,但作为一种脚本语言,生成异常对象的工作量相对正常/对解析来说很小)。

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