如何安全合理地确定指针是否指向某个指定缓冲区?

时间:2015-01-04 14:34:09

标签: c++ pointers undefined-behavior

我希望实现一个确定给定指针是否指向给定缓冲区的函数。规范:


template <typename T>
bool points_into_buffer (T *p, T *buf, std::size_t len);

如果有n0 <= n && n < lenp == buf + n,则返回true

否则,如果某个n0 <= n && n < len * sizeof(T)reinterpret_cast<char *>(p) == reinterpret_cast<char *>(buf) + n,则行为未定义。

否则,返回false


明显的实现看起来像

template <typename T>
bool points_into_buffer (T *p, T *buf, std::size_t len) {
    return p >= buf && p < buf + len;
}

但是在标准C ++中有未定义的行为:指针的关系比较仅针对指向同一数组的指针定义。

另一种方法是使用标准库的比较器对象:

template <typename T>
bool points_into_buffer (T *p, T *buf, std::size_t len) {
    return std::greater_equal<T *>()(p, buf) && std::less<T *>()(p, buf + len);
}

保证在我希望它返回true时返回true,并避免未定义的行为,但允许误报:给定int a; int b;,它允许true的结果1}} points_into_buffer(&a, &b, 1)

它可以实现为循环:

template <typename T>
bool points_into_buffer (T *p, T *buf, std::size_t len) {
    for (std::size_t i = 0; i != len; i++)
        if (p == buf + i)
            return true;
    return false;
}

但是,编译器无法优化该循环。

是否有一种有效的写法,在启用当前编译器和优化的情况下,结果是在恒定时间内确定的?

2 个答案:

答案 0 :(得分:8)

据我所知,这是我所能实现的功能的可移植实现:

#ifdef UINTPTR_MAX

bool points_into_buffer(std::uintptr_t p, std::uintptr_t buf, std::size_t len)
{
  const auto diff = p + 0u - buf;
  if (diff < len)
    // #1
    if (reinterpret_cast<char *>(p) == reinterpret_cast<char *>(buf) + diff)
      return true;
  for (std::size_t n = 0; n != len; n++)
    if (reinterpret_cast<char *>(p) == reinterpret_cast<char *>(buf) + n)
      // #2
      if (reinterpret_cast<char *>(p) - reinterpret_cast<char *>(buf) != diff)
        return true;
  return false;
}

template <typename T>
bool points_into_buffer(T *p, T *buf, std::size_t len)
{
  return points_into_buffer(reinterpret_cast<std::uintptr_t>(p),
                            reinterpret_cast<std::uintptr_t>(buf),
                            len * sizeof(T));
}

#else

template <typename T>
bool points_into_buffer(T *p, T *buf, std::size_t len)
{
  for (std::size_t n = 0; n != len; n++)
    if (p == buf + n)
      return true;
  return false;
}

#endif

通常,diff不保证具有有意义的值。但是没关系:当且仅当它找到true n时才返回reinterpret_cast<char *>(p) == reinterpret_cast<char *>(buf) + n。它仅使用diff作为提示,以更快地找到n的值。

它依赖于编译器优化条件,这些条件通常在编译时不一定是已知的,但在编译时对于特定平台是已知的。标记为if#1的{​​{1}}语句的条件由GCC在编译时确定为始终分别为#2true,因为{定义了{1}},允许GCC看到循环内没有执行任何有用的操作,并允许删除整个循环。

falsediff生成的代码如下所示:

bool points_into_buffer(char*, char*, unsigned int):
        movl    4(%esp), %edx
        movl    $1, %eax
        movl    12(%esp), %ecx
        subl    8(%esp), %edx
        cmpl    %edx, %ecx
        ja      L11
        xorl    %eax, %eax
L11:    rep ret

bool points_into_buffer(int*, int*, unsigned int):
        movl    4(%esp), %edx
        movl    12(%esp), %eax
        subl    8(%esp), %edx
        leal    0(,%eax,4), %ecx
        movl    $1, %eax
        cmpl    %edx, %ecx
        ja      L19
        xorl    %eax, %eax
L19:    rep ret

points_into_buffer<char>不可用的系统上,或地址比简单整数更复杂的系统上,而是使用循环。

答案 1 :(得分:1)

如果将指针强制转换为足够大的无符号整数并添加字节数而不是对象数,则未定义的行为就会消失。

template <typename T>
bool points_into_buffer (T *p, T *buf, std::size_t len) {
    uintptr_t ip = (uintptr_t)p;
    uintptr_t ibuf = (uintptr_t)buf;
    return ip >= ibuf && ip < (ibuf + sizeof(T) * len);
}

此代码无法检测p是否未正确对齐,但您可以轻松添加带有%的测试。