【问题标题】:How do I safely and sensibly determine whether a pointer points somewhere into a specified buffer?如何安全、明智地确定指针是否指向指定缓冲区的某个位置?
【发布时间】:2015-03-02 05:25:15
【问题描述】:

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


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

如果有一些n0 &lt;= n &amp;&amp; n &lt; len,对于其中p == buf + n,返回true

否则,如果有一些n0 &lt;= n &amp;&amp; n &lt; len * sizeof(T),其中reinterpret_cast&lt;char *&gt;(p) == reinterpret_cast&lt;char *&gt;(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;,它允许points_into_buffer(&amp;a, &amp;b, 1) 的结果为true

可以实现为循环:

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

但是,编译器在优化该循环时遇到了麻烦。

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

【问题讨论】:

  • 是什么让您认为使用比较函子不会导致未定义的行为?它们被定义为使用普通的关系运算符,因此这里应该没有区别。
  • @Horstling C++11 [comparisons]p8 "对于模板 greaterlessgreater_equalgreater_equalless_equal,任何指针类型的特化都会产生一个总顺序,甚至如果内置运算符 &lt;&gt;&lt;=&gt;= 没有。"
  • @hvd 有趣(而且有点违反直觉)。不知道那个,谢谢。
  • 从语言律师的角度来看,这根本没有帮助,但在实践中知道即使是 GNU 可移植性库 (gnulib) makes the same assumption 似乎也没有帮助据报道,这在现实世界的平台上会中断。
  • @jdlugosz 这又假设了一个线性地址空间,这很常见,但不是通用的,我想避免假设。

标签: c++ pointers undefined-behavior


【解决方案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 是否未正确对齐,但您可以轻松添加带有 % 的测试。

【讨论】:

  • 这不保证适用于所有实现。您假设一个线性地址空间,这是常见的,但不是通用的。
  • 好点。这是一个定义的行为(实际上是定义的实现),但它可能不是一个正确的行为。我不确定是否有独立于平台的解决方案。
【解决方案2】:

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

#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&lt;char *&gt;(p) == reinterpret_cast&lt;char *&gt;(buf) + n。它只使用diff 作为提示以更快地找到n 的值。

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

points_into_buffer&lt;char&gt;points_into_buffer&lt;int&gt; 的生成代码如下所示:

bool points_into_buffer(char*, char*, unsigned int): movl 4(%esp), %edx 移动 $1, %eax movl 12(%esp), %ecx subl 8(%esp), %edx cmpl %edx, %ecx 雅L11 xorl %eax, %eax L11:代表 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 移动 $1, %eax cmpl %edx, %ecx 雅L19 xorl %eax, %eax L19:重复

std::uintptr_t 不可用的系统上,或者地址比简单整数更复杂的系统上,使用循环代替。

【讨论】:

  • hvd:知道了。这很脏,但很聪明。
猜你喜欢
  • 2022-08-18
  • 2015-02-03
  • 1970-01-01
  • 1970-01-01
  • 2021-01-13
  • 1970-01-01
  • 1970-01-01
  • 2015-04-15
  • 2018-06-02
相关资源
最近更新 更多