【问题标题】:Check whether pointer points to vector element检查指针是否指向向量元素
【发布时间】:2018-06-16 07:25:18
【问题描述】:

我想要一个函数来检查指针是否指向向量的元素:

template <typename T>
bool pointsToElement(const std::vector<T>& vec, const T* ptr);

该函数是一个辅助函数,用于从指针安全地创建迭代器:

template <typename T>
std::vector<T>::iterator toIterator(std::vector<T>& vec, T* ptr)
{
    assert(pointsToElement(vec, ptr));
    return vec.begin() + (ptr - &vec[0]);
}

“显而易见”的方式类似于:

template <typename T>
bool pointsToElement(const std::vector<T>& vec, const T* ptr)
{
    if (vec.empty())
        return false;
    return ptr >= &vec.front() && ptr <= &vec.back();
}

不幸的是,这似乎调用了未定义的行为,因为它可能会比较指向不同对象的指针。

一种安全的方法是:

template <typename T>
bool pointsToElement(const std::vector<T>& vec, const T* ptr)
{
    for (auto& elem : vec) {
        if (&elem == ptr)
            return true;
    }
    return false;
}

但这当然是 O(N),所以非常不可取。

我可以想象另一种方式:

template <typename T>
bool pointsToElement(const std::vector<T>& vec, const T* ptr)
{
    if (vec.empty())
        return false;
    intptr_t pos   = reinterpret_cast<intptr_t>(ptr);
    intptr_t begin = reinterpret_cast<intptr_t>(&vec.front());
    intptr_t end   = reinterpret_cast<intptr_t>(&vec.back());
    return pos >= begin && pos <= end;
}

我认为该标准不能保证 inptr_t 上的任何排序,但这至少不应调用任何未定义的行为并在主要平台上产生正确的结果(我目前只关心 Linux 和 Windows)。

这个分析正确吗?有没有更好的方法来检查指针是否在一定范围内?

(注意:有几个类似的问题,但没有一个考虑使用强制转换为 intptr_t 的可能性)。

【问题讨论】:

  • intptr_t 不是你的朋友:与比较普通指针相比,比较它的保证更少。例如,甚至不能保证单个指针能够可靠地转换为单个整数(但所有这些整数都必须convert back to the same pointer)。
  • @DavisHerring 它的意图是uintptr_t 具有到整数的自然映射
  • 当然是有意的:第 4 段是这么说的。它是由实现定义的,而不是未指定的,所以你的实现很可能会说它很好而且很明显。但这并不能使intptr_t 改进内置指针比较,后者是未指定,而不是未定义行为
  • @DavisHerring 感谢您的更正。我认为比较不相关的指针是未定义的行为,但根据stackoverflow.com/questions/31774683/…,只有减去不相关的指针才是 UB,比较它们只是未指定的行为。对吗?
  • 是的,那是expr.add/5,它还包括在同一个数组中减去相距太远的指针的不幸情况(通常只对char* 可能)。

标签: c++ undefined-behavior


【解决方案1】:

这是std::lessstd::greater 派上用场的情况之一。

任何指针类型的 std::less 特化都会产生严格的总顺序,即使内置 operator、=)。

有了这些保证,您就可以使用“显而易见”的解决方案。


DavisHerring 在 cmets 中指出,可以存在具有分段内存布局的架构,其中不相关的指针可以在两个向量元素之间排序,并且仍然符合严格的总顺序。

【讨论】:

  • 是什么阻止了总订单在 vector 的两个元素之间包含不相关的指针?
  • @DavisHerring 向量将其数据存储在连续内存中,因此两个元素之间不能有不相关的指针。
  • 当然向量是连续的——但是指向不相关对象的指针不按内置运算符排序,因此没有什么限制它在总顺序中的位置不位于元素之间。
  • @DavisHerring 我正在重新阅读这句话,我认为理论上你是对的(取决于你如何解释它)。不过,我看不出这在上述总订单的任何实施中有何意义。在标准中查找相关段落可能会给出更详细的定义。
  • 那是相关的段落(连同expr.rel/3.3)。它旨在支持指针比较可能涉及分析控制寄存器和页表的分段架构。
【解决方案2】:

首先:

如果你需要检查一个指针是否有效,你有一个普遍的设计问题!您必须考虑谁拥有数据以及指向该数据的指针在多长时间内有效。

根据您的想法构建一个“检查”指针是否指向向量元素的函数:

    template < typename T>
bool check( const std::vector<T>& vec, const T* ptr )
{   
    const T* start = vec.data();
    const T* end = start + vec.size();

    std::cout << start << " " << end << " " << ptr << std::endl;

    // this did NOT check if pointer points to start of a element boundary!
    if (( ptr >= start ) && ( ptr < end ))
    {   
        return true;
    }

    return false;
}

int main()
{
    std::vector<int> vec{0,1,2,3,4,5};

    std::cout << check( vec, &vec[0]) << std::endl;
    std::cout << check( vec, &vec[5]) << std::endl;
    std::cout << check( vec, &vec[6]) << std::endl;
}

但这有什么帮助呢?

如果您的指针被分配给向量的某个元素,然后该向量重新分配了一些时间,则它可能再次位于该位置但将某些元素移开了。

一般来说:如果您的设计会导致指针悬空,那么以后进行任何运行时检查是没有意义的。稍后在运行时检查时无法修复损坏的设计!您的程序仍有“未定义的行为!”

【讨论】:

  • 在某些情况下,使用可识别为标识有效静态持续时间的共享对象或未共享分配对象的指针是有意义的。例如,一个函数通常可能会返回四个字符串之一,但有时会返回完全不同的东西。虽然可以让函数始终返回一个新分配的字符串,但通过从公共池中返回一个字符串并让析构函数避免释放该池中的字符串来处理常见情况可能会更有效。跨度>
  • 警告:如果 ptr 在 vec 之外,您的 check() 函数会调用未定义的行为,因此编译器可以正确地将函数内容替换为“return true”。我的代码的目标是进行有效的调试检查以及早发现错误,这很有价值,即使它没有捕获所有错误。
  • @tbleher:“如果”你的 check() 函数调用未定义的行为,为什么?你能解释一下我的代码有什么问题吗?谢谢!
猜你喜欢
  • 2011-03-12
  • 2011-06-07
  • 2020-03-29
  • 1970-01-01
  • 2015-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多