为什么指针减法是C++中未定义的行为?

Ahm*_*shi 3 c++ pointers

对于下面的例子,可能会导致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);
}
Run Code Online (Sandbox Code Playgroud)

我没有找到答案在C中指针减法何时未定义?

Bat*_*eba 9

指针算法(包括两个指针的减法)仅在指针指向同一数组中的元素或超过该数组末尾的元素时定义.在此上下文中,标量计为大小为1的数组.

在任何其他实例中允许指针运算是没有意义的.要做到这一点会不必要地限制C的内存模型,并可能削弱其灵活性和移植到异国情调架构的能力.

  • 当然,推论是像OP提出的“in_range()”函数几乎完全没有意义。您需要已经知道安全调用该函数的答案。 (2认同)

Jer*_*fin 5

对于代码,正如您编写的那样,C ++的答案与C基本上相同:只有且仅当所涉及的两个指针引用同一数组的一部分或结束于数组的末尾时,您才能定义行为。 (其中,如@bathsheba所指出的,非数组对象被视为与一项的数组相同)。

C ++确实,但是,添加一个皱纹这可能是有用的,但是在这里认识:即使没有减,也没有下令比较(例如<)在需要时应用于指针的对象分开,以产生有意义的结果std::less<T>和朋友,从<functional> 要求做所以。因此,给定两个独立的对象,如下所示:

Object a;
Object b;
Run Code Online (Sandbox Code Playgroud)

...将两个对象的地址与比较对象进行比较,必须“产生严格的总顺序,这些顺序在这些专业之间是一致的,并且还与内置运算符<,>,<=,>施加的部分顺序一致=。” (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);
}
Run Code Online (Sandbox Code Playgroud)

如果您确实想维护原始函数签名,则也可以这样做:

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);
}
Run Code Online (Sandbox Code Playgroud)

[请注意,我std::less_equal在第一次比较时使用了它,std:less第二次使用它来匹配C ++通常期望的语义,其中范围定义为[begin, end)。]

但是,这确实带有一个条件:您需要确保r指向至少包含n1的数组的开头,否则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);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您只需传递数组的名称和要测试的指针:

int foo[10];
int *bar = &foo[4];

std::cout << std::boolalpha << in_range(foo, bar) << "\n"; // returns true
Run Code Online (Sandbox Code Playgroud)

不过,这支持针对实际阵列进行测试。如果您尝试将非数组项作为第一个参数传递,它将无法编译:

int foo[10];
int bar;
int *baz = &foo[0];
int *ptr = new int[20];

std::cout << std::boolalpha << in_range(bar, baz) << "\n"; // won't compile
std::cout << std::boolalpha << in_range(ptr, baz) << "\n"; // won't compile either
Run Code Online (Sandbox Code Playgroud)

前者可能会预防一些事故。后者可能不是很理想。如果我们要同时支持这两种情况,则可以通过重载来实现(对于这三种情况,如果我们选择支持):

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);
}

template <class Ty>
bool in_range(Ty &a, Ty *b) { return &a == b; }

template <class Ty>
bool in_range(Ty a, Ty b, size_t N) {
    return std::less_equal<Ty>()(a, b) && 
           std::less<Ty>()(b, a + N);
}

void f() { 
     double foo[10]; 
     double *x = &foo[0]; 
     double bar;
     double *baz = new double[20];

     std::cout << std::boolalpha << in_range(foo, x) << "\n";
     std::cout << std::boolalpha << in_range(bar, x) << "\n";
     std::cout << std::boolalpha << in_range(baz, x, 20) << "\n";
}
Run Code Online (Sandbox Code Playgroud)

1.如果您想真正地掌握技术,则不必指向数组的开头,只需指向数组的一部分,至少n要跟在数组中。