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

问题描述 投票:2回答:2

对于下面的例子,什么可能导致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);
}

我没有在When is pointer subtraction undefined in C?找到答案

c++ pointers
2个回答
4
投票

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

然而,C ++确实增加了一条可能对此有用的皱纹:尽管在应用于指向单独对象的指针时,无论是减法还是有序比较(例如,<)都需要产生有意义的结果,std::less<T>和朋友,来自<functional>要求这样做。所以,给出两个单独的对象:

Object a;
Object b;

...将两个对象的地址与比较对象进行比较必须“产生一个严格的总顺序,这些顺序在这些特化之间是一致的,并且与内置运算符强加的部分顺序一致<,>,<=,> =“。 (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 ++的典型预期语义,其中范围定义为[begin, end)。 ]

这确实带有一个附带条件:你需要确保r指向至少n items1的数组的开头,否则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

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

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

前者可能会阻止一些事故。后者可能不太理想。如果我们想要支持两者,我们可以通过重载来实现(对于所有三种情况,如果我们选择):

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

1.如果你想获得真正的技术,它不必指向数组的开头 - 它只需指向一个数组的一部分,后面至少是数组中的n项。


9
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.