如何在c/c++中检查两个任意内存范围是否重叠

How to check that two arbitrary memory ranges are not overlapped in c/c++

本文关键字:任意 两个 内存 范围 重叠 是否 范围是 c++ 检查      更新时间:2023-10-16

假设我需要实现一个非常通用的实用程序函数,它必须将一个缓冲区复制到另一个缓冲区时。根据c/c++标准,使用具有重叠内存的memcpy是UB,另一方面memmove可能较慢。我希望能够在任何可能的时候使用memcmp实现,并在必要时切换到memmove

void copy(const void* src, int src_size, void* dst, int dst_size, int count)
{
assert(src);
asserc(dst);
assert(0 < count);
assert(count < src_size);
assert(count < dst_size);
if (are_overlaped(src, dst, count))
std::memove(dst, src, count);
else
std::memcmp(dst, src, count);
}

根据c/c++标准,与任意指针进行比较或相减是UB。为了编写一个简单的are_overlaped实现,两个指针都必须指向同一数组的项。所以这是UB在一般情况下

bool are_overlaped(const void* first, const void* second, int size)
{
assert(first);
asserc(second);
assert(0 < size);
return std::abs(reinterpret_cast<const char*>(first)
- reinterpret_cast<const char*>(second)) < size;
}

所以我的问题是如何正确实现任意指针的are_overlaped?或者有标准的检查方法吗?

PS。带副本的示例只是一个更好理解的上下文。我想知道如何实现are_overlaped,而不是使用或实现复制。

指针上的比较是UB,如果这两个指针不属于同一个数组,则可以使用std::less,类似于:

template <typename T>
bool are_overlaped(const T* first, const T* second, int size)
{
// `first` and `second` should be arrays of size greater or equal to `size`
return first && second
&& (!std::less<>{}(second, first) && std::less<>{}(second, first + size)
|| !std::less<>{}(first, second) && std::less<>{}(first, second + size));

}

这是合法的:

bool inside( void const* a, void const* b, size_t count ){
if (count==0) return false;
if (count==1) return ptr_equal(a,b);
return inside(a, b, count/2)||inside(a,static_cast<char const*>(b)+count/2, count-count/2 );
}

其中CCD_ 2执行CCD_ 3或CCD_;我认为第二个定义很好,但第一个是等价的,也会很好地定义。

下一个

bool overlap(void const* lhs, size_t lhs_size, void const* rhs, size_t rhs_size ){
if (!lhs_size || !rhs_size) return false;
auto a0 = static_cast<char const*>(lhs);
auto b0 = static_cast<char const*>(rhs);
return inside(a0, b0, rhs_size) || inside(a0+lhs_size-1, b0, rhs_size) || inside(b0, a0, lhs_size) || inside(b0+rhs_size-1, a0, lhs_size);
}

祈祷你的优化器能在理智的平台上完成它的工作。好吧,检查一下。

我对这两个答案都想了很多。尽管@Yakk-Adam Nevraumont是完全正确的,但他的解决方案不太可能有用。另一方面,@Jarod42的答案是可用的,但不是100%正确。我们能把这两种解决方案混合在一起,以获得足够的正确性和合理的价格吗?当然,我认为这是有限制的。

bool may_be_overlapped(void const* lhs, size_t lhs_size_in_bytes, void const* rhs, size_t rhs_size_in_bytes)
{
if (lhs_size_in_bytes == 0 || rhs_size_in_bytes == 0)
return false;
assert(lhs != nullptr);
assert(rhs != nullptr);
if (std::less<>(reiterpret_cast<const char*>(lhs) + lhs_size_in_bytes - 1, rhs)
|| std::less<>(reiterpret_cast<const char*>(rhs) + rhs_size_in_bytes - 1, lhs))
return false;
return true;
}
// wide contract
[[nodiscard]]
bool safe_copy(void* dst, size_t dst_size_in_bytes, const void* src, size_t src_size_in_bytes, size_t count_in_bytes)
{
if (count_in_bytes == 0)
return true;
if (dst == nullptr || src == nullptr
|| dst_size_in_bytes < count_in_bytes
|| src_size_in_bytes < count_in_bytes)
{
return false;
}
std::memmove(dst, src, count_in_bytes);
return true;
}
// narrow contract
void not_overlaped_copy(void* dst, size_t dst_size_in_bytes, const void* src, size_t src_size_in_bytes, size_t count_in_bytes)
{
if (count_in_bytes == 0)
return;
assert(dst != nullptr);
assert(src != nullptr);
assert(dst_size_in_bytes >= count_in_bytes);
assert(src_size_in_bytes >= count_in_bytes);
assert(!may_be_overlapped(dst, count_in_bytes, src, count_in_bytes));
std::memcpy(dst, src, count_in_bytes);
}
// narrow contract
void copy(void* dst, size_t dst_size_in_bytes, const void* src, size_t src_size_in_bytes, size_t count_in_bytes)
{
if (count_in_bytes == 0)
return;
assert(dst != nullptr);
assert(src != nullptr);
assert(dst_size_in_bytes >= count_in_bytes);
assert(src_size_in_bytes >= count_in_bytes);
std::memmove(dst, src, count_in_bytes);
}

PS。如果您确实需要are_overlapped,它可以在不考虑内存复制上下文的情况下使用,那么下面是Yakk解决方案的set_intersection版本:

bool are_overlapped(void const* lhs, size_t lhs_size_in_bytes, void const* rhs, size_t rhs_size_in_bytes)
{
if (lhs_size_in_bytes == 0 || rhs_size_in_bytes == 0)
return false;
assert(lhs != nullptr);
assert(rhs != nullptr);
auto first1 = reiterpret_cast<const char*>(lhs);
const auto last1 = first1 + lhs_size_in_bytes - 1;
auto first2 = reiterpret_cast<const char*>(rhs);
const auto last2 = first2 + rhs_size_in_bytes - 1;
if (std::less<>(last1, first2) || std::less<>(last2, first1))
return false;
while (first1 <= last1 && first2 <= last2)
{
if (std::less<>(first1, first2))
++first1;
else if (std::less<>(first2, first1))
++first2;
else
retun true;
}
return false;
}

不要那样做
memmove和memcpy一样高效,只是检查了一次重叠。