从堆栈地址形成指针范围是不确定的行为?

一些C或C ++程序员惊讶地发现即使存储无效指针也是未定义的行为 。 但是,对于堆或堆栈数组,可以存储一个超过数组末尾的地址,这允许您存储“结束”位置以便在循环中使用。

但是从单个堆栈变量形成指针范围是未定义的行为,如:

char c = 'X'; char* begin = &c; char* end = begin + 1; for (; begin != end; ++begin) { /* do something */ } 

虽然上面的例子很没用,但是如果某个函数需要一个指针范围,并且你只有一个值来传递它,那么这可能很有用。

这是未定义的行为吗?

这是允许的,行为是定义的, beginend都是安全派生的指针值

在C ++标准第5.7节( [expr.add] )第4段中:

出于这些运算符的目的,指向非arrays对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型。

使用C时,可以在C99 / N1256标准6.5.6第7节中找到类似的条款。

出于这些运算符的目的,指向不是数组元素的对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型。


顺便说一句,在3.7.4.3节( [basic.stc.dynamic.safety] )“安全派生指针”中有一个脚注:

本节不对取消引用未由::operator new分配的内存的指针施加限制。 这保持了许多C ++实现使用二进制库和用其他语言编写的组件的能力。 特别是,这适用于C二进制文件,因为取消引用指向malloc分配的malloc指针不受限制。

这表明整个堆栈中的指针算法是实现定义的行为,而不是未定义的行为。

我相信,从法律上讲,您可以将单个对象视为大小为1的数组。 此外,只要没有取消引用,将指针放在任何数组的末尾之外是绝对合法的。 所以我相信它不是UB。

只要您不取消引用无效迭代器,它就不是未定义的行为。
您可以在分配之外保留指向内存的指针,但不允许取消引用它。

ISO14882:2011(e)的5.7-5规定:

当一个具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。 如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。 换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i – 第n个元素,只要它们存在。 此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素。 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义。

除非我忽略了某些内容,否则添加仅适用于指向同一数组的指针。 对于其他一切,最后一句适用:“否则,行为未定义”

编辑:的确,当你添加5.7-4时,你发现你所做的操作(虚拟地)在一个数组上,因此该句子不适用:

出于这些运算符的目的,指向非arrays对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型。

一般来说,指向超出内存空间的是未定义的行为,但是“一个接一个结束”的例外情况,根据标准是有效的。

因此,在特定示例中, &c+1是有效指针但无法安全地解除引用。

您可以将c定义为大小为1的数组:

char c[1] = { 'X' };

然后,未定义的行为将成为定义的行为。 结果代码应该相同。