简体   繁体   English

复制 C 中的 mem 块时防止 memory 混叠

[英]Preventing memory aliasing when copying mem blocks in C

I think I'm getting rusty, so please bare with me.我想我生锈了,所以请和我一起裸露。 I'll try to be brief.我会尽量简短。

Q1. Q1。 When trying to copy buffers, say buf2 to buf1 , does the following check suffice against aliasing?当试图复制缓冲区时,比如buf2buf1 ,以下检查是否足以防止混叠?

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

Q2. Q2。 If so, can we selectively use either memcopy() or memmove() depending on the case, like this?如果是这样,我们是否可以根据情况选择性地使用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
}

It works but I'm not comfortable at all with forcefully casting the type-qualifiers of the parameters when switching to memmove() .它可以工作,但是当切换到memmove()时,我对强制转换参数的类型限定符一点也不满意 I think the standard confirms my doubts (can never find these darn things when I need them... using C99 btw), but since the code works I'd like to be extra sure, because if it's ok like that it would save me from duplicating buf2 , work with the duplicate and freeing it when done.我认为标准证实了我的怀疑(当我需要它们时永远找不到这些该死的东西......使用C99顺便说一句),但由于代码有效,我想更加确定,因为如果它可以这样它会拯救我从复制buf2开始,使用副本并在完成后释放它。

I believe that the term "memory areas overlap" is used more frequently.我相信术语“内存区域重叠”使用得更频繁。

There is no portable way of doing this kind of pointer comparisons.没有可移植的方式来进行这种指针比较。 Standard library implementations have to compare the pointers but in this case the author of the library knows exactly how this comparison works.标准库实现必须比较指针,但在这种情况下,库的作者确切地知道这种比较是如何工作的。

Most popular glibc implementation use unsigned long long or unsigned long integers to compare the pointers (or rather perform the address artthmetics).最流行的 glibc 实现使用unsigned long longunsigned long整数来比较指针(或者更确切地说执行地址算法)。

Q2. Q2。 If so, can we selectively use either memcopy() or memmove() depending on the case, like this如果是这样,我们是否可以根据情况有选择地使用 memcopy() 或 memmove() ,像这样

It makes no sense as remove checks it itself.这是没有意义的,因为 remove 会检查它本身。 Most implementations I know do not follow the C standard way of moving memory areas - ie do not create any temporary arrays only decide in which direction to copy the memory areas. Most implementations I know do not follow the C standard way of moving memory areas - ie do not create any temporary arrays only decide in which direction to copy the memory areas. If memory areas do not overlap the copy operation is the same fast as when using memcpy .如果 memory 区域不重叠,则复制操作与使用memcpy时的速度相同。

Most popular implementation (gnu C library glibc):最流行的实现(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);
}

For any two generic pointers, you can't really do pointer arithmetic on them.对于任何两个通用指针,您都不能真正对它们进行指针运算。 This is regulated by the additive operators C17 6.5.6/8:这是由加法运算符 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;如果指针操作数和结果都指向同一个数组 object 的元素,或数组 object 的最后一个元素,则评估不应产生溢出; otherwise, the behavior is undefined.否则,行为未定义。

Similar text exists for the relational operators (6.5.8) - any two pointers getting compared with them must point at the same array or otherwise the behavior is undefined.关系运算符 (6.5.8) 存在类似的文本 - 与它们进行比较的任何两个指针必须指向同一个数组,否则行为未定义。

You can in theory convert the pointers to integers in the form of uintptr_t and do arithmetic on that one.理论上,您可以将指针转换为uintptr_t形式的整数并对其进行算术运算。 If you know for certain that buf1 points at the beginning of an array of buf1size items, then you could in theory calculate if buf2 points at the same array or not, by doing integer arithmetic on uintptr_t .如果您确定buf1指向buf1size项数组的开头,那么理论上您可以通过对uintptr_t执行 integer 算术来计算buf2是否指向同一数组。 But there isn't much to gain from that.但这并没有什么好处。

Instead you could simply write your function as相反,您可以简单地将 function 写为

void func (char* restrict buf1, char* restrict buf2);

And push the responsibility of ensuring that the two buffers don't alias onto the caller.并将确保两个缓冲区不别名的责任推给调用者。

As for your function pointer selection of either memcpy or memmove , then apparently the mainstream compilers (gcc, clang) seem to ignore that one version has restrict qualified pointers.至于你的 function 指针选择memcpymemmove ,那么显然主流编译器(gcc,clang)似乎忽略了一个版本restrict合格的指针。 If that's conforming behavior or not, I'm not sure.如果这是否符合行为,我不确定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM