Harry K.
Harry K.

Reputation: 640

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. When trying to copy buffers, say buf2 to buf1, does the following check suffice against aliasing?

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

Q2. If so, can we selectively use either memcopy() or memmove() depending on the case, like this?

// 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(). 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.

Upvotes: 0

Views: 204

Answers (2)

Lundin
Lundin

Reputation: 214395

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:

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.

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.

You can in theory convert the pointers to integers in the form of uintptr_t and do arithmetic on that one. 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. But there isn't much to gain from that.

Instead you could simply write your function as

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. If that's conforming behavior or not, I'm not sure.

Upvotes: 1

0___________
0___________

Reputation: 67810

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).

Q2. If so, can we selectively use either memcopy() or memmove() depending on the case, like this

It makes no sense as remove checks it itself. 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.

Most popular implementation (gnu C library 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);
}

Upvotes: 1

Related Questions