17

Can I check whether or not a given pointer points to an object within an array, specified by its bounds?

template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
    return begin <= p && p < end;
}

Or do the pointer comparisons invoke undefined behavior if p points outside the bounds of the array? In that case, how do I solve the problem? Does it work with void pointers? Or is it impossible to solve?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 2
    Your comparison works, but doesn't really guarantee that 'end' is anything to do with the array - I reckon your better off using a size (see my answer below). – BeeBand Jan 11 '11 at 13:29
  • @jweyrich? Why do you say that? Assuming the OP only uses well-defined pointer conversions, every pointer in the program points to well-aligned data (or they're `null`). Unaligned data essentially doesn't exist according to the C++ standard. `p` is guaranteed to be aligned unless there is undefined behavior at the call site. – jalf Jan 11 '11 at 13:33
  • @jalf: I retracted my comment. Thanks for the lesson though. – jweyrich Jan 11 '11 at 14:49
  • @BeeBand: that's not considered by anyone below but this is so right. – Schultz9999 Jan 13 '11 at 08:53

6 Answers6

11

Although the comparison is valid only for pointers within the array and "one past the end", it is valid to use a set or map with a pointer as the key, which uses std::less<T*>

There was a big discussion on this way back in 1996 on comp.std.c++

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • Is it because std::less somehow magically avoids the comparison? – Maxim Egorushkin Jan 11 '11 at 13:24
  • 1
    @Maxim: std::less is required to implement whatever magic is needed to get consistent results. It may indicate that address `0A00:0001` is less than `0100:0001`, but it may not indicate they are equivalent. `operator<` is allowed to compare only the offsets and indicate those addresses are equivalent. – Bart van Ingen Schenau Jan 11 '11 at 13:47
  • Could you refer me to any standard C++ standard library implementation which does that? – Maxim Egorushkin Jan 11 '11 at 14:03
  • 6
    However, there is no requirement that when you do apply `std::less` to pointers from different objects that one from one range isn't ordered between the bounds of a second range making the fact that you can compare them moot. – CB Bailey Jan 11 '11 at 14:04
  • @Maxim, all standard libraries do that. The problem is that finding implementations which have standard comparison operators not based on a total order. I'm not sure there are still many compilers targeting segmented modes of x86. – AProgrammer Jan 11 '11 at 14:20
8

Straight from the MSDN documentation:

Two pointers of different types cannot be compared unless:

  • One type is a class type derived from the other type.
  • At least one of the pointers is explicitly converted (cast) to type void *. (The other pointer is implicitly converted to type void * for the conversion.)

So a void* can be compared to anything else (including another void*). But will the comparison produce meaningful results?

If two pointers point to elements of the same array or to the element one beyond the end of the array, the pointer to the object with the higher subscript compares higher. Comparison of pointers is guaranteed valid only when the pointers refer to objects in the same array or to the location one past the end of the array.

Looks like not. If you don't already know that you are comparing items inside the array (or just past it), then the comparison is not guaranteed to be meaningful.

There is, however, a solution: The STL provides std::less<> and std::greater<>, which will work with any pointer type and will produce valid results in all cases:

if (std::less<T*>()(p, begin)) {
    // p is out of bounds
}

Update:

The answer to this question gives the same suggestion (std::less) and also quotes the standard.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
  • 3
    I would not quote MSDN as a definitive C++ source as it is often inaccurate or MS-specific. – Maxim Egorushkin Jan 11 '11 at 13:27
  • @Maxim Yegorushkin: I can agree with that. But they 've done lots of work to point out MS-specific issues and greatly improved that problem. Plus, this is just confirming what I already "knew". And finally, IMO MSDN is immeasurably easier to search than the standard. – Jon Jan 11 '11 at 13:30
  • Your quote talks about pointers of different types, while the OP is interested in comparing pointers of the same type. – Péter Török Jan 11 '11 at 13:46
  • @Peter: the OP asked "Does it work with void pointers?". That's what the first part addresses; I thought it was pretty clear since I explicitly call that out. – Jon Jan 11 '11 at 13:48
  • @Jon, you are right, my omission, the question is indeed there, albeit as a secondary one within the text. The code sample shown defines a function with `T*` parameters though, so they are supposed to be of the same type. If you allow me a suggestion to make your answer clearer, quoting the specific (sub)question you are answering could be an improvement. – Péter Török Jan 11 '11 at 14:05
  • @Jon: thanks for the link to Johannes' answer, it does clear things. Funny things is that he himself references another of his answers to a question about operations on pointers: http://stackoverflow.com/questions/1418068/what-are-the-operations-supported-by-raw-pointer-and-function-pointer-in-c-c/1418152#1418152 – Matthieu M. Jan 11 '11 at 14:47
  • That linked question addresses using a pointer type as a key, which is different from checking whether a pointer is in a range. `std::less` may be suitable for the former but not necessarily the later. – CB Bailey Jan 11 '11 at 14:55
8

The only correct way to do this is an approach like this.

template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
    for (; begin != end; ++begin)
    {
        if (p == begin)
            return true;
    }
    return false;
}

Fairly obviously, this doesn't work if T == void. I'm not sure whether two void* technically define a range or not. Certainly if you had Derived[n], it would be incorrect to say that (Base*)Derived, (Base*)(Derived + n) defined a valid range so I can't see it being valid to define a range with anything other than a pointer to the actual array element type.

The method below fails because it is unspecified what < returns if the two operands don't point to members of the same object or elements of the same array. (5.9 [expr.rel] / 2)

template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
    return !(p < begin) && (p < end);
}

The method below fails because it is also unspecified what std::less<T*>::operator() returns if the two operands don't point to members of the same object or elements of the same array.

It is true that a std::less must be specialized for any pointer type to yield a total order if the built in < does not but this is only useful for uses such as providing a key for a set or map. It is not guaranteed that the total order won't interleave distinct arrays or objects together.

For example, on a segmented memory architecture the object offset could be used for < and as the most significant differentiator for std::less<T*> with the segment index being used to break ties. In such a system an element of one array could be ordered between the bounds of a second distinct array.

template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
    return !(std::less<T*>()(p, begin)) && (std::less<T*>()(p, end));
}
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • Can you use == to compare pointers like this, e.g. on a segmented architecture? http://stackoverflow.com/q/4909766/511601 – Fred Nurk Feb 05 '11 at 21:28
  • 1
    @FredNurk: `==` has to be able to compare pointers that come from different complete objects, if that involves comparing the segment identifiers then that is what the implementation has to do. (If there's such a thing as "the current segment" and this can change then the segment identifier must be part of what is stored as a pointer; you wouldn't expect to get a different object if you derefenced the same pointer value in a different context.) – CB Bailey Feb 06 '11 at 01:23
5

The C++ standard does not specify what happens when you are comparing pointers to objects that do not reside in the same array, hence undefined behaviour. However, the C++ standard is not the only standard your platform must conform. Other standards like POSIX specify things that C++ standard leaves as undefined behaviour. On platforms with virtual address space like Linux and Win32/64 you can compare any pointers without causing any undefined behaviour.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

comparisions on pointer types don't neccesarily result in a total order. std::less/std::greater_equal do, however. So ...

template <typename T>
bool points_within_array(T* p, T* begin, T* end)
{
    return std::greater_equal<T*>()(p, begin) && std::less<T*>()(p, end);
}

will work.

etarion
  • 16,935
  • 4
  • 43
  • 66
0

Could you not do this with std::distance, i.e. your problem effectively boils down to:

return distance(begin, p) >= 0 && distance(begin, p) < distance(begin, end);

Given this random access iterator (pointer) is being passed in, it should boil down to some pointer arithmetic rather than pointer comparisons? (I'm assuming end really is end and not the last item in the array, if the last then change the less than to <=).

I could be way off the mark...

Nim
  • 33,299
  • 2
  • 62
  • 101