在 C 中复制内存块时防止内存别名

问题描述

我觉得我快生锈了,所以请不要打扰我。我会尽量简短。

第一季度。尝试复制缓冲区时,例如 buf2buf1,以下检查是否足以防止混叠?

if (buf2 >= buf1 && buf2 < buf1 + buf1size) {
    // aliasing
}

第 2 季度。如果是这样,我们是否可以根据情况选择性地使用 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 longunsigned long 整数来比较指针(或执行地址运算)。

第 2 季度。如果是这样,我们可以有选择地使用 memcopy() 或 memmove() 视情况而定,像这样

这没有意义,因为 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,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,len);

      /* Fall out and copy the tail.  */
    }

      /* There are just a few bytes to copy.  Use byte memory operations.  */
      BYTE_COPY_FWD (dstp,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,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,len);

      /* Fall out and copy the tail.  */
    }

      /* There are just a few bytes to copy.  Use byte memory operations.  */
      BYTE_COPY_BWD (dstp,len);
    }

  RETURN (dest);
}