有些C或C ++程序员惊讶地发现即使是storing an invalid pointer is undefined behavior。但是,对于堆或堆栈数组,可以存储一个超过数组末尾的地址,这允许您存储“结束”位置以便在循环中使用。
但是从单个堆栈变量形成指针范围是未定义的行为,如:
char c = 'X';
char* begin = &c;
char* end = begin + 1;
for (; begin != end; ++begin) { /* do something */ }
虽然上面的例子很没用,但是如果某个函数需要一个指针范围,那么这可能很有用,而且你只有一个值来传递它。
这是未定义的行为吗?
答案 0 :(得分:14)
这是允许的,行为已定义,begin
和end
都是安全派生的指针值。
在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 :(得分:4)
我相信,从法律上讲,您可以将单个对象视为大小为1的数组。此外,只要没有取消引用,将指针放在任何数组末尾之外是绝对合法的。所以我相信它不是UB。
答案 2 :(得分:3)
只要您不取消引用无效迭代器,它就不是未定义的行为。
您可以在分配之外保留指向内存的指针,但不允许取消引用它。
答案 3 :(得分:2)
添加或减去具有整数类型的表达式时 从指针开始,结果具有指针操作数的类型。如果 指针操作数指向数组对象的元素和数组 足够大,结果指向一个偏离的元素 原始元素使得下标的差异 结果和原始数组元素等于整数表达式。 换句话说,如果表达式P指向一个的第i个元素 数组对象,表达式(P)+ N(等效地,N +(P))和(P)-N (其中N的值为n)分别指向i + n和i - 数组对象的第n个元素,只要它们存在即可。而且,如果 表达式P指向数组对象的最后一个元素,即 表达式(P)+1指向数组对象的最后一个元素, 如果表达式Q指向一个数组的最后一个元素 对象,表达式(Q)-1指向数组的最后一个元素 宾语。如果指针操作数和结果都指向元素 对于相同的数组对象,或者超过数组对象的最后一个元素,评估不应产生溢出;否则,行为是 未定义。
除非我忽略了某些内容,否则添加仅适用于指向同一数组的指针。对于其他一切,最后一句适用:“否则,行为未定义”
修改强> 实际上,当您添加5.7-4时,事实证明您所做的操作(虚拟地)在数组上,因此该句子不适用:
出于这些运算符的目的,指向非阵列对象的指针 行为与指向数组的第一个元素的指针相同 长度为1,对象的类型为元素类型。
答案 4 :(得分:0)
一般来说,指向超出内存空间的行为是未定义的行为,但是“一个接一个结束”有一个例外,根据标准是有效的。
因此,在特定示例中,&c+1
是有效指针,但无法安全地解除引用。
答案 5 :(得分:-3)
您可以将c定义为大小为1的数组:
char c[1] = { 'X' };
然后,未定义的行为将成为定义的行为。 结果代码应该相同。