从堆栈地址形成指针范围是未定义的行为吗

Is it undefined behavior to form a pointer range from a stack address?

本文关键字:未定义 范围是 指针 堆栈 栈地址      更新时间:2023-10-16

一些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段中:

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

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

就这些运算符而言,指向非数组元素的对象的指针的行为与指向长度为1的数组的第一个元素的指针相同,该对象的类型为其元素类型


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

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

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

我相信在法律上,您可以将单个对象视为大小为1的数组。此外,只要指针没有被反引用,就可以将其带过任何数组的末尾。所以我相信这不是UB。

只要不取消引用无效的迭代器,它就不是未定义的行为
您可以持有超出分配范围的内存指针,但不允许取消引用。ISO14882:2011(e)的

5.7-5规定:

当对具有整型的表达式进行加法运算或减法运算时从指针中,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向从原始元素,使得所得数组元素和原始数组元素等于积分表达式。换句话说,如果表达式P指向数组对象,表达式(P)+N(相当于N+(P))和(P)-N(其中N的值为N)分别指向第i+N个和第i−数组对象的第n个元素,前提是它们存在。此外,如果表达式P指向数组对象的最后一个元素表达式(P)+1指向数组对象的最后一个元素之后的一个,并且如果表达式Q指向数组的最后一个元素之后的一个对象,表达式(Q)-1指向数组的最后一个元素对象如果指针操作数和结果都指向元素对于相同的数组对象,或数组对象最后一个元素之后的一个,评估不应产生溢出;否则,行为为未定义。

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

编辑:事实上,当你加5.7-4时,你所做的操作(实际上)是在一个数组上,因此这句话不适用:

对于这些运算符,指向非数组对象的指针行为与指向的数组的第一个元素的指针相同length一,将对象的类型作为其元素类型。

通常情况下,指向内存空间之外是未定义的行为,但"过终点一个"除外,根据标准,这是有效的。

因此,在特定示例中,&c+1是一个有效的指针,但不能安全地取消引用。

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

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

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