对于下面的示例,可能导致undefined behavior
的原因是什么?和为什么?
#include <cstddef>
#include <iostream>
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n)
{
return 0 < (test - r) && (test - r) < (std::ptrdiff_t)n;
}
void f() {
double foo[10];
double *x = &foo[0];
double bar;
std::cout << std::boolalpha << in_range(&bar, x, 10);
}
中找不到答案
答案 0 :(得分:9)
指针算法,包括两个指针的减法,仅在指针指向同一数组中的元素或超过该数组末尾的元素时才定义。在此上下文中,标量计为大小为1的数组。
在任何其他实例中允许指针运算是没有意义的。要做到这一点会不必要地限制C的内存模型,并可能削弱其灵活性和移植到异国情调架构的能力。
答案 1 :(得分:4)
对于代码,正如您所编写的那样,C ++的答案基本上与C的答案相同:当且仅当涉及的两个指针引用同一数组的部分时,才会得到定义的行为,或者在结束之前(如@bathsheba已经指出的那样,非数组对象被视为与一个项目的数组相同)。
然而, C ++确实增加了一条可能对此有用的皱纹:即使在进行有意义的结果时,也不需要减法或有序比较(例如,<
)应用于指向单独对象的指针,std::less<T>
和朋友,<functional>
需要这样做。所以,给出两个单独的对象:
Object a;
Object b;
...将两个对象的地址与比较对象进行比较必须&#34;产生在这些特化之间一致的严格总顺序,并且与内置运算符强加的部分顺序一致&lt; ,&gt;,&lt; =,&gt; =。&#34; (N4659,[比较] / 2)。
因此,您可以编写如下函数:
template <typename Ty>
bool in_range(const Ty *test, const Ty *begin, const Ty *end)
{
return std::less_equal<Ty>()(begin, test) && std::less<Ty>()(test, end);
}
如果你真的想要维护原始的功能签名,你也可以这样做:
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n)
{
auto end = r + n;
return std::less_equal<Ty>()(r, test) && std::less<Ty>()(test, end);
}
[请注意,我已使用std::less_equal
为第一次比较编写了它,第二次使用std:less
来匹配C ++的典型预期语义,其中范围定义为{{ 1}}。 ]
这确实带有一个附带条件:您需要确保[begin, end)
指向至少r
项 1 的数组的开头,否则{ {1}}将产生未定义的行为。
至少对于我期望的这种函数的典型用例,你可以通过传递数组本身来简化使用,而不是指针和显式长度:
n
在这种情况下,您只需传递数组的名称和要测试的指针:
auto end = r + n;
此仅支持对实际数组进行测试。如果您尝试传递一个非数组项作为第一个参数,它只是不会编译:
template <class Ty, size_t N>
bool in_range(Ty (&array)[N], Ty *test) {
return std::less_equal<Ty *>()(&array[0], test) &&
std::less<Ty *>()(test, &array[0] + N);
}
前者可能会阻止一些事故。后者可能不是很理想。如果我们想要支持两者,我们可以通过重载来实现(对于所有三种情况,如果我们选择):
int foo[10];
int *bar = &foo[4];
std::cout << std::boolalpha << in_range(foo, bar) << "\n"; // returns true
1.如果你想获得真正的技术,它不必指向数组的开头 - 它必须指向一个数组的一部分,后面跟着至少{{1}数组中的项目。 功能
答案 2 :(得分:1)
在这种情况下,未定义的行为通常不会导致崩溃,但是会导致无意义或不一致的结果。
在大多数现代体系结构中,减去2个不相关的指针只是计算地址差除以指针类型的大小,大约是:
A *p1, *p2;
...
ptrdiff_t diff = ((intptr_t)p2 - (intptr_t)p1) / (intptr_t)sizeof(*p1);
行为异常的架构示例是英特尔的16位分段大中型机型:
NULL
的指针通常被优化为将段部分与0进行一次比较SS
,但是对于从堆分配的2个对象,您可以拥有p <= q && q <= p
和{{ 1}},或者p != q
与p - q == 0
一起被未定义的行为覆盖。