When two pointers are subtracted, both must point to elements of the same array object or to one past the last element of the array object; the result is the difference of the subscripts of the two array elements. Similarly, when two iterators are subtracted (including via std::distance()), both iterators must refer to the same container object or must be obtained via a call to end() (or cend()) on the same container object.

If two unrelated iterators (including pointers) are subtracted, the operation results in undefined behavior [ISO/IEC 14882-2014]. Do not subtract two iterators (including pointers) unless both point into the same container or one past the end of the same container.

Noncompliant Code Example

This noncompliant code example attempts to determine whether the pointer test is within the range [r, r + n]. However, when test does not point within the given range, as in this example, the subtraction produces 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);
}

Noncompliant Code Example

In this noncompliant code example, the in_range() function is implemented using a comparison expression instead of subtraction. The C++ Standard, [expr.rel], paragraph 4 [ISO/IEC 14882-2014], states the following:

If two operands p and q compare equal, p<=q and p>=q both yield true and p<q and p>q both yield false. Otherwise, if a pointer p compares greater than a pointer q, p>=q, p>q, q<=p, and q<p all yield true and p<=q, p<q, q>=p, and q>p all yield false. Otherwise, the result of each of the operators is unspecified.

Thus, comparing two pointers that do not point into the same container or one past the end of the container results in unspecified behavior. Although the following example is an improvement over the previous noncompliant code example, it does not result in portable code and may fail when executed on a segmented memory architecture (such as some antiquated x86 variants). Consequently, it is noncompliant.

#include <iostream>
 
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n) {
  return test >= r && test < (r + n);
}
 
void f() {
  double foo[10];
  double *x = &foo[0];
  double bar;
  std::cout << std::boolalpha << in_range(&bar, x, 10);
}

Noncompliant Code Example

This noncompliant code example is roughly equivalent to the previous example, except that it uses iterators in place of raw pointers. As with the previous example, the in_range_impl() function exhibits unspecified behavior when the iterators do not refer into the same container because the operational semantics of a < b on a random access iterator are b - a > 0, and >= is implemented in terms of <.

#include <iostream>
#include <iterator>
#include <vector>

template <typename RandIter>
bool in_range_impl(RandIter test, RandIter r_begin, RandIter r_end, std::random_access_iterator_tag) {
  return test >= r_begin && test < r_end;
}
 
template <typename Iter>
bool in_range(Iter test, Iter r_begin, Iter r_end) {
  typename std::iterator_traits<Iter>::iterator_category cat;
  return in_range_impl(test, r_begin, r_end, cat);
}
 
void f() {
  std::vector<double> foo(10);
  std::vector<double> bar(1);
  std::cout << std::boolalpha << in_range(bar.begin(), foo.begin(), foo.end());
}

Noncompliant Code Example

In this noncompliant code example, std::less<> is used in place of the < operator. The C++ Standard, [comparisons], paragraph 14 [ISO/IEC 14882-2014], states the following:

For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not.

Although this approach yields a total ordering, the definition of that total ordering is still unspecified by the implementation. For instance, the following statement could result in the assertion triggering for a given, unrelated pair of pointers, a and b: assert(std::less<T *>()(a, b) == std::greater<T *>()(a, b));. Consequently, this noncompliant code example is still nonportable and, on common implementations of std::less<>, may even result in undefined behavior when the < operator is invoked.

#include <functional>
#include <iostream>
 
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n) {
  std::less<const Ty *> less;
  return !less(test, r) && less(test, r + n);
}
 
void f() {
  double foo[10];
  double *x = &foo[0];
  double bar;
  std::cout << std::boolalpha << in_range(&bar, x, 10);
}

Compliant Solution

This compliant solution demonstrates a fully portable, but likely inefficient, implementation of in_range() that compares test against each possible address in the range [r, n]. A compliant solution that is both efficient and fully portable is currently unknown.

#include <iostream>
 
template <typename Ty>
bool in_range(const Ty *test, const Ty *r, size_t n) {
  auto *cur = reinterpret_cast<const unsigned char *>(r);
  auto *end = reinterpret_cast<const unsigned char *>(r + n);
  auto *testPtr = reinterpret_cast<const unsigned char *>(test);
 
  for (; cur != end; ++cur) {
    if (cur == testPtr) {
      return true;
    }
  }
  return false;
}
 
void f() {
  double foo[10];
  double *x = &foo[0];
  double bar;
  std::cout << std::boolalpha << in_range(&bar, x, 10);
}

Risk Assessment

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

CTR54-CPP

Medium

Probable

Medium

P8

L2

Automated Detection

Tool

Version

Checker

Description

Astrée

22.10

invalid_pointer_subtraction
invalid_pointer_comparison

CodeSonar
8.1p0

LANG.STRUCT.CUP
LANG.STRUCT.SUP

Comparison of Unrelated Pointers
Subtraction of Unrelated Pointers

Helix QAC

2024.1

DF2668, DF2761, DF2762, DF2763, DF2766, DF2767, DF2768


LDRA tool suite
9.7.1

 

70 S, 87 S, 437 S, 438 S

Enhanced Enforcement

Parasoft C/C++test

2023.1

CERT_CPP-CTR54-a
CERT_CPP-CTR54-b
CERT_CPP-CTR54-c

Do not compare iterators from different containers
Do not compare two unrelated pointers
Do not subtract two pointers that do not address elements of the same array

Polyspace Bug Finder

R2023b

CERT C++: CTR54-CPPChecks for subtraction or comparison between iterators from different containers (rule partially covered).

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[Banahan 2003]Section 5.3, "Pointers"
Section 5.7, "Expressions Involving Pointers" 
[ISO/IEC 14882-2014]

Subclause 5.7, "Additive Operators"
Subclause 5.9, "Relational Operators"
Subclause 20.9.5, "Comparisons"



7 Comments

  1. The <code> tag is what you want to use on Confluence to express code; it will display the code unbutchered. As to your C++ questions:

    The only way your is_in_bounds() function can take valid parameters, exhibit no undefined behavior, and yet return false is if ptr == array + size. If ptr is outside the interval of array, then you will have undefined behavior. On many platforms, including x86, your is_in_bounds() will still return false as you expect, but this is not guaranteed by the C or C++ standards.

    This implementation of is_in_bounds() behaves as you want on all platforms, without undefined behavior (assuming array is a valid array and size correctly indicates its size):

    bool is_in_bounds (char *array, int size, char *ptr) {
      for (char* p = array; p < array + size; p++) {
        if (p == ptr) {
          return true;
        }
      }
      return false;
    }
    

    However this implementation has inferior performance to yours. (smile)

    1. That's annoying...

      What does the standard say about intptr_t?  Does it guarantee that  intptr_t (&foo[n]) == intptr_t(foo) + n*sizeof(*foo) ?

      As int types, any two intptr_t values should be less-than comparable... If that relation is valid, then doing the quick test with intptr_ts should be equivalent to the slow test with intptr_ts, which should be equivalent to the slow test with pointers.

      Unless the standard says only that intptr_ts can be losslessly converted to/from pointers to void and anything beyond that is up to the implementation.

       

      (I read that paper on undefined behavior and am now really paranoid about my safety checks being deleted by the optimizer.)

      1. Paranoia is in style these days. :)

        For details regarding intptr_t, I will refer you to INT36-C. Converting a pointer to integer or integer to pointer. Which basically says that most ptr<->int conversion is implementation-defined. AFAIK there is no guarantee regarding the mathematical values of any pointers (eg no guarantee that the integer values array and array+1 actually are consecutive). The one exception is that the null pointer always equals 0.

        1. Is there any way to determine what a compiler does for implementation-defined semantics?  So I could do something like:

          #ifndef __STANDARD_INTPTR_T_ARITHMETIC
          #error Compiler does not support standard intptr_t arithmetic
          #endif

           

          If my program won't compile on, say, a segment-offset system, fine.  If my compiler changes how it uses intptr_t and causes my assumptions to break, I'd rather have my program refuse to compile than compile to bad code.

          1. I agree that failure to compile is much preferable over compiling to bad code.

            When the C (or C++) standard says something is implementation-defined, that means that the implementation must do one of a handful of choices, and must document it. So the platform you are using is therefore required to document how it handles int-pointer conversion.

  2. I've heard claims that std::less has well-defined semantics for comparing any two pointers, but I couldn't find it written in the standard.  Can anybody find confirmation of that?

    1. [comparisons]p14 says, "For templates greater, less, greater_equal, and less_equal, the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not."