在 C 中复制内存块时防止内存别名
Preventing memory aliasing when copying mem blocks in C
我觉得我生疏了,所以请裸露我。我会尽量简短。
Q1。尝试复制缓冲区时,将 buf2 说成 buf1,以下检查是否足以防止混叠?
if (buf2 >= buf1 && buf2 < buf1 + buf1size) {
// aliasing
}
Q2。如果是这样,我们是否可以根据情况有选择地使用 memcopy()
或memmove()
,像这样?
// use memcpy() by default
void *(*funcp)(void *restrict, const void *restrict, size_t) = &memcpy;
// switch to memmove() when aliasing
if ( aliasing ) {
// this cast FORCEFULLY changes the type-qualifiers of the declared parameters
funcp = (void *(*)(void *, const void *, size_t)) &memmove;
}
// later on ...
if ( buf2size <= buf1size ) {
(*funcp)( buf1, buf2, buf2size ); // funcp() works too, I prefer making it explicit
}
它可以工作,但我 一点都不舒服 在切换到 memmove()
时强制转换参数的类型限定符。我认为该标准证实了我的怀疑(当我需要它们时永远找不到这些该死的东西......使用 C99 顺便说一句),但由于代码有效我想更加确定,因为如果它没问题它会救我从复制 buf2,处理副本并在完成后释放它。
我相信“内存区域重叠”这个词用得比较频繁。
没有可移植的方式来进行这种指针比较。标准库实现必须比较指针,但在这种情况下,库的作者确切地知道这种比较是如何进行的。
最流行的 glibc 实现使用 unsigned long long
或 unsigned long
整数来比较指针(或者更确切地说执行地址算法)。
Q2. If so, can we selectively use either memcopy() or memmove()
depending on the case, like this
没有意义,因为 remove 会自行检查。我知道的大多数实现都不遵循移动内存区域的 C 标准方式——即不创建任何临时数组只决定复制内存区域的方向。如果内存区域不重叠,复制操作与使用 memcpy
.
时一样快
最流行的实现(gnu C 库 glibc):
rettype
inhibit_loop_to_libcall
MEMMOVE (a1const void *a1, a2const void *a2, size_t len)
{
unsigned long int dstp = (long int) dest;
unsigned long int srcp = (long int) src;
/* This test makes the forward copying code be used whenever possible.
Reduces the working set. */
if (dstp - srcp >= len) /* *Unsigned* compare! */
{
/* Copy from the beginning to the end. */
#if MEMCPY_OK_FOR_FWD_MEMMOVE
dest = memcpy (dest, src, len);
#else
/* If there not too few bytes to copy, use word copy. */
if (len >= OP_T_THRES)
{
/* Copy just a few bytes to make DSTP aligned. */
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
/* Copy whole pages from SRCP to DSTP by virtual address
manipulation, as much as possible. */
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
/* Copy from SRCP to DSTP taking advantage of the known
alignment of DSTP. Number of bytes remaining is put
in the third argument, i.e. in LEN. This number may
vary from machine to machine. */
WORD_COPY_FWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
}
/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_FWD (dstp, srcp, len);
#endif /* MEMCPY_OK_FOR_FWD_MEMMOVE */
}
else
{
/* Copy from the end to the beginning. */
srcp += len;
dstp += len;
/* If there not too few bytes to copy, use word copy. */
if (len >= OP_T_THRES)
{
/* Copy just a few bytes to make DSTP aligned. */
len -= dstp % OPSIZ;
BYTE_COPY_BWD (dstp, srcp, dstp % OPSIZ);
/* Copy from SRCP to DSTP taking advantage of the known
alignment of DSTP. Number of bytes remaining is put
in the third argument, i.e. in LEN. This number may
vary from machine to machine. */
WORD_COPY_BWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
}
/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_BWD (dstp, srcp, len);
}
RETURN (dest);
}
对于任何两个泛型指针,你不能真正对它们进行指针运算。这是由加法运算符 C17 6.5.6/8:
规定的
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
关系运算符 (6.5.8) 存在类似文本 - 与它们进行比较的任何两个指针必须指向同一数组,否则行为未定义。
理论上,您可以将指针转换为 uintptr_t
形式的整数,然后对其进行算术运算。如果您确定 buf1
指向 buf1size
项数组的开头,那么理论上您可以通过整数运算计算 buf2
是否指向同一数组在 uintptr_t
上。但这并没有什么好处。
相反,您可以简单地将函数编写为
void func (char* restrict buf1, char* restrict buf2);
并将确保两个缓冲区不会别名的责任推给调用者。
至于您选择 memcpy
或 memmove
的函数指针,显然主流编译器(gcc、clang)似乎忽略了一个版本具有 restrict
合格指针。这是否符合规范,我不确定。
我觉得我生疏了,所以请裸露我。我会尽量简短。
Q1。尝试复制缓冲区时,将 buf2 说成 buf1,以下检查是否足以防止混叠?
if (buf2 >= buf1 && buf2 < buf1 + buf1size) {
// aliasing
}
Q2。如果是这样,我们是否可以根据情况有选择地使用 memcopy()
或memmove()
,像这样?
// use memcpy() by default
void *(*funcp)(void *restrict, const void *restrict, size_t) = &memcpy;
// switch to memmove() when aliasing
if ( aliasing ) {
// this cast FORCEFULLY changes the type-qualifiers of the declared parameters
funcp = (void *(*)(void *, const void *, size_t)) &memmove;
}
// later on ...
if ( buf2size <= buf1size ) {
(*funcp)( buf1, buf2, buf2size ); // funcp() works too, I prefer making it explicit
}
它可以工作,但我 一点都不舒服 在切换到 memmove()
时强制转换参数的类型限定符。我认为该标准证实了我的怀疑(当我需要它们时永远找不到这些该死的东西......使用 C99 顺便说一句),但由于代码有效我想更加确定,因为如果它没问题它会救我从复制 buf2,处理副本并在完成后释放它。
我相信“内存区域重叠”这个词用得比较频繁。
没有可移植的方式来进行这种指针比较。标准库实现必须比较指针,但在这种情况下,库的作者确切地知道这种比较是如何进行的。
最流行的 glibc 实现使用 unsigned long long
或 unsigned long
整数来比较指针(或者更确切地说执行地址算法)。
Q2. If so, can we selectively use either memcopy() or memmove() depending on the case, like this
没有意义,因为 remove 会自行检查。我知道的大多数实现都不遵循移动内存区域的 C 标准方式——即不创建任何临时数组只决定复制内存区域的方向。如果内存区域不重叠,复制操作与使用 memcpy
.
最流行的实现(gnu C 库 glibc):
rettype
inhibit_loop_to_libcall
MEMMOVE (a1const void *a1, a2const void *a2, size_t len)
{
unsigned long int dstp = (long int) dest;
unsigned long int srcp = (long int) src;
/* This test makes the forward copying code be used whenever possible.
Reduces the working set. */
if (dstp - srcp >= len) /* *Unsigned* compare! */
{
/* Copy from the beginning to the end. */
#if MEMCPY_OK_FOR_FWD_MEMMOVE
dest = memcpy (dest, src, len);
#else
/* If there not too few bytes to copy, use word copy. */
if (len >= OP_T_THRES)
{
/* Copy just a few bytes to make DSTP aligned. */
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
/* Copy whole pages from SRCP to DSTP by virtual address
manipulation, as much as possible. */
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
/* Copy from SRCP to DSTP taking advantage of the known
alignment of DSTP. Number of bytes remaining is put
in the third argument, i.e. in LEN. This number may
vary from machine to machine. */
WORD_COPY_FWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
}
/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_FWD (dstp, srcp, len);
#endif /* MEMCPY_OK_FOR_FWD_MEMMOVE */
}
else
{
/* Copy from the end to the beginning. */
srcp += len;
dstp += len;
/* If there not too few bytes to copy, use word copy. */
if (len >= OP_T_THRES)
{
/* Copy just a few bytes to make DSTP aligned. */
len -= dstp % OPSIZ;
BYTE_COPY_BWD (dstp, srcp, dstp % OPSIZ);
/* Copy from SRCP to DSTP taking advantage of the known
alignment of DSTP. Number of bytes remaining is put
in the third argument, i.e. in LEN. This number may
vary from machine to machine. */
WORD_COPY_BWD (dstp, srcp, len, len);
/* Fall out and copy the tail. */
}
/* There are just a few bytes to copy. Use byte memory operations. */
BYTE_COPY_BWD (dstp, srcp, len);
}
RETURN (dest);
}
对于任何两个泛型指针,你不能真正对它们进行指针运算。这是由加法运算符 C17 6.5.6/8:
规定的If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
关系运算符 (6.5.8) 存在类似文本 - 与它们进行比较的任何两个指针必须指向同一数组,否则行为未定义。
理论上,您可以将指针转换为 uintptr_t
形式的整数,然后对其进行算术运算。如果您确定 buf1
指向 buf1size
项数组的开头,那么理论上您可以通过整数运算计算 buf2
是否指向同一数组在 uintptr_t
上。但这并没有什么好处。
相反,您可以简单地将函数编写为
void func (char* restrict buf1, char* restrict buf2);
并将确保两个缓冲区不会别名的责任推给调用者。
至于您选择 memcpy
或 memmove
的函数指针,显然主流编译器(gcc、clang)似乎忽略了一个版本具有 restrict
合格指针。这是否符合规范,我不确定。