为什么在C ++中指针减法是未定义的行为? [英] Why pointer subtraction is undefined behavior in C++?

查看:170
本文介绍了为什么在C ++中指针减法是未定义的行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于以下示例,什么可能导致undefined behavior?和为什么?

For the example below, what may cause undefined behavior? and why?

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

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

推荐答案

对于代码,正如您编写的那样,对于C ++和对于C,答案基本上是相同的:只有且仅当以下情况时,您才能获得已定义的行为:所涉及的两个指针指向同一数组的一部分,或者指向其末尾的一部分(如@bathsheba已经指出的,非数组对象被视为与一个项的数组相同).

For the code, as you've written it, the answer is basically the same for C++ as it is for C: you get defined behavior if and only if the two pointers involved refer to parts of the same array, or one past its end (where, as @bathsheba already noted, a non-array object is treated as being the same as an array of one item).

C ++确实增加了一个皱纹,可能使 很有用:即使将减法或有序比较(例如<)应用于以下情况,也不需要产生有意义的结果为此,需要指向<functional> 中的单独对象std::less<T>和朋友的指针.因此,给定两个独立的对象,如下所示:

C++ does, however, add one wrinkle that might be useful to know about here: even though neither subtraction nor ordered comparisons (e.g., <) is required to produce meaningful results when applied to pointers to separate objects, std::less<T> and friends, from <functional> are required to do so. So, given two separate objects like this:

Object a;
Object b;

...将两个对象的地址与比较对象进行比较必须产生严格的总顺序,该顺序在这些专业之间是一致的,并且还与内置运算符< ;,>所施加的部分顺序一致, < =,> =." (N4659,[比较]/2).

...comparing the addresses of the two objects with the comparison objects must "yield a strict total order that is consistent among those specializations and is also consistent with the partial order imposed by the built-in operators <, >, <=, >=." (N4659, [comparisons]/2).

这样,您可以将函数编写如下:

As such, you could write your function something like this:

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

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

If you really want to maintain the original function signature, you could do that as well:

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). ]

[Note that I've written it using std::less_equal for the first comparison, and std:less for the second to match the typically expected semantics of C++, where the range is defined as [begin, end). ]

这确实带有一个条件:您需要确保r指向至少包含n个项 1 的数组的开头,否则auto end = r + n;会产生未定义的行为.

This does carry one proviso though: you need to ensure that r points to to the beginning of an array of at least n items1, or else the auto end = r + n; will produce undefined behavior.

至少对于我期望的此类功能的典型用例,您可以通过传递数组本身(而不是指针和显式长度)来稍微简化用法:

At least for what I'd expect as the typical use-case for such a function, you can probably simplify usage a little but by passing the array itself, rather than a pointer and explicit length:

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

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

In this case, you'd just pass the name of the array, and the pointer you want to test:

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

std::cout << std::boolalpha << in_range(foo, bar) << "\n"; // returns true

虽然 only 支持对实际数组的测试.如果您尝试将非数组项作为第一个参数传递,它将无法编译:

This only supports testing against actual arrays though. If you attempt to pass a non-array item as the first parameter it simply won't compile:

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

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

The former probably prevents some accidents. The latter might not be quite as desirable. If we want to support both, we can do so via overloading (for all three situations, if we choose to):

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个项目即可.


1. If you want to get really technical, it doesn't have to point to the beginning of the array--it just has to point to part of an array that's followed by at least n items in the array.

这篇关于为什么在C ++中指针减法是未定义的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆