根据C++标准,`char* p=0; std::equal(p,p,p)`是否定义良好?

11

根据C++标准,以下内容是否定义良好?

char* p = 0;
std::equal(p, p, p);

问题实际上是这样的:

标准是否要求std::equal(begin1, end1, begin2)被实现为如果begin1 == end1,那么begin1begin2可以是任何指针,甚至是不指向有效内存对象的指针?

我认为这是标准的意图,但我没有找到一个明确说明这一点的声明。

我关心的原因是,VisualStudio似乎在begin1 == end1时尝试检查begin2的“有效性”,这与我对标准要求的理解相矛盾。

编辑:这是我认为违反标准的VS 2012代码:

template<class _InIt1, class _InIt2> inline
bool equal(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2)
{   // compare [_First1, _Last1) to [First2, ...)
    _DEBUG_RANGE(_First1, _Last1);
    _DEBUG_POINTER(_First2);
    return (_Equal1(_Unchecked(_First1), _Unchecked(_Last1), _First2, _Is_checked(_First2)));
}

template<class _Ty> inline
void _Debug_pointer(const _Ty *_First, _Dbfile_t _File, _Dbline_t _Line)
{   // test iterator for non-singularity, const pointers
    if (_First == 0)
        _DEBUG_ERROR2("invalid null pointer", _File, _Line);
}

我认为这是明确定义的。 - chris
2
@NeilKirk:它们不是无效的;只是不能被解引用。你仍然可以将begin1end1进行比较,因此算法是良好定义的,因为它只会执行该比较而不尝试解引用任何内容。 - Mike Seymour
1
@NeilKirk:空指针是一个有效的指针,因此也是一个有效的迭代器。它是单一的,不可解引用,但仍可以与另一个空指针进行比较,因此[null,null)是一个有效的范围。 - Mike Seymour
@MikeSeymour 一个有效的指针但不是有效的迭代器。 - Neil Kirk
1
@NeilKirk: 我不知道你为什么这么说,或者在这种情况下你认为“有效”是什么意思。它是一个单一的迭代器(如C++11 24.2.1/5所述),但它与自身比较相等,因此可以从自身到达(如24.2.1/6定义),因此与自身形成一个有效(空)范围(如24.2.1/7定义)。这个函数的唯一要求是两个输入范围是有效的;由两个空指针形成的空范围满足该要求,无论Microsoft怎么想。 - Mike Seymour
显示剩余2条评论
4个回答

9
我们有一个编号为25.2.1/1的规则,其内容如下:
返回:如果范围[first1, last1)中的每个迭代器i都满足以下相应条件,则返回true:*i == *(first2 + (i - first1)), pred(*i, *(first2 + (i - first1))) != false。否则,返回false。
在您的情况下,范围[0, 0)内没有迭代器,因此“every”范围内的迭代器都通过了测试,但不需要执行任何实际测试(因为不存在要测试的范围内的迭代器)。
对我来说看起来像是VisualStudio的一个bug。

以这种方式编写,它可能不是一个bug,而是一种不同的解释。也就是说,在范围[first,last)中进行检查意味着存在一个范围。由于[0,0)没有元素,因此它没有范围,因此是无效的输入。 - Zac Howland
1
@ZacHowland:标准非常明确:没有对范围非空的先决条件,并且此处的规范清楚地要求如果为空,则返回“false”。 - Mike Seymour
@MikeSeymour 我认为你的意思是“规范在这里明确要求如果它为空则返回 **true**” - 正如Mike也指出的那样。你能确认一下吗? - Kristian Spangsege
1
@ZacHowland:是的,抱歉,我写成了“false”,实际上应该是“true”。我的意思是,对于任何有效的输入范围,包括像这样的空范围,它都是明确定义的。 - Mike Seymour
@MikeSeymour 正如我之前所说,我并不完全不同意,但是现在已经没有必要充当魔鬼的代言人了,因为他正在使用迭代器调试级别设置为默认值的调试版本进行操作。 - Zac Howland
显示剩余6条评论

2

正如@Zac所指出的,这个检查是为了安全起见,Visual Studio变得更加挑剔。如果你想让Visual Studio即使在调试版本中更加符合标准,可以通过将宏_ITERATOR_DEBUG_LEVEL设置为0来关闭此行为。


我不怀疑你所说的是真的,我也知道VS有不严格遵守标准的传统,但正在改进。然而,默认启用与标准冲突的调试功能是不恰当的。我希望他们最终会解决这个问题。 - Kristian Spangsege
@KristianSpangsege 我明白你的意思,但我不会抱太大希望。我猜他们更想要更具有防黑客能力的Windows软件,而这些潜在的烦恼并不重要,特别是当他们提供关闭它的机制时。 - zdan

0

通过您的更新,现在清楚地知道这不是标准的违规,而是一个调试检查。如果您在发布模式下编译它,这些检查将不会运行,并且该函数将与标准描述相匹配。

在调试模式下获得这些信息非常有用,因为它将帮助您跟踪一些难以找到的错误。


我不同意。他们检查得太多了。这妨碍了完全正确和理智的代码。他们可以轻松修改他们的调试工具,使其符合标准,并在范围不为空时仍然捕获空指针。我非常确定这只是一个错误或者“思维漏洞”。从代数的角度来看(我相信也是从标准的角度来看),将不可引用的指针作为空范围的一部分传递并没有任何问题或错误。 - Kristian Spangsege
这只是一个警告: 警告1 警告 C4996: 'std::_Equal1': 带有可能不安全的参数的函数调用 - 此调用依赖于调用方检查传递的值是否正确。要禁用此警告,请使用-D_SCL_SECURE_NO_WARNINGS。请参阅有关如何使用Visual C++“Checked Iterators”的文档,还有一个调试断言(您可以禁用它)。 - Zac Howland
标准并不完全适用于调试版本。调试器会使用各种技巧来帮助查找/预防/指出在发布版本中难以追踪的潜在问题(这是标准所适用的)。如果调试断言和警告让您感到困扰,您可以将它们都禁用掉。 - Zac Howland
我正在跨平台库的上下文中工作,因此我必须要求所有应用程序禁用检查。对我来说更好的解决方案是添加额外的代码来检查特殊情况,但这是一种麻烦,并且对读者来说似乎是多余的。虽然不是一个很大的问题,但这是一个本不必要的复杂性。 - Kristian Spangsege
由于每个编译器都有自己的标志需要设置,因此您无论如何都必须为每个编译器设置这样的一组标志。 - Zac Howland
显示剩余2条评论

0
C++11标准在24.2.1/5中声明:“[i,i)是一个空范围”。
然而,在24.2.1/5中,它首先暗示0必须是一个奇异值,然后声明“对于奇异值,大多数表达式的结果未定义”。然后列出了未定义行为的例外情况,但比较不包括在内。
因此,也许比较奇异迭代器的相等性是未定义的,因此使[i,i)无法评估。
这也表明您的运行时错误发生在名为_Equal1()的函数内部。
我认为标准在这方面是含糊不清的,我并不确定这是否是Visual Studio 2012中的错误。
在章节“1213.有效和奇异迭代器的含义未指定”中的http://cplusplus.github.io/LWG/lwg-unresolved.html也很有趣,涉及到这种混淆...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接