3

While learning c I have implemented my own memcpy functions. I have used a wider type( uint32_t) in the function. (For simplicity the function is restricted to types that are multiples of 4 and the data is properly aligned )

void memcpy4( void* dst , void* src , int size )
{
    size /= 4;

    for ( int i = 0 ; i < size ; i++ )
        ((uint32_t*)dst)[i] = ((uint32_t*)src)[i];
}

I did some reading on type punning and strict aliasing and I believe the function above breaks the rule. The correct implementation would be this since you can use a char:

void memcpy4( void* dst , void* src , int size )
{
    for ( int i = 0 ; i < size ; i++ )
        ((char *)dst)[i] = ((char *)src)[i];
}

I tried to do some casting trough an union, but that turned out to be invalid as well.

How could such function be implemented with a wider type and not break the strict aliasing rule?

alk
  • 69,737
  • 10
  • 105
  • 255
this
  • 5,229
  • 1
  • 22
  • 51
  • Is `size` in bytes? If so, you wouldn't need to cast through a union; let the `memcpy4` function do copying, and let other functions deal with their data in their own way... For the wider type, note that `int count=size/sizeof(uint32_t);` should be sufficient for the first function. – abiessu Jan 19 '14 at 13:10
  • @abiessu Size are bytes. I simplified the function for readability. Of course in the full implementation there is a check for that. – this Jan 19 '14 at 13:13
  • @PaulR divide by 2 is a typo. It used to be a right shift. – this Jan 19 '14 at 13:14
  • I'm sorry, a check for what again? Also, you could just `#define MY_IMPLEMENTED_COPY_TYPE uint32_t` and then do `int count=size/sizeof(MY_IMPLEMENTED_COPY_TYPE);` and later `((MY_IMPLEMENTED_COPY_TYPE*)dst)[i] = ((MY_IMPLEMENTED_COPY_TYPE*)src)[i];` – abiessu Jan 19 '14 at 13:15
  • 10
    Since this is a learning exercise, let me suggest you another thing to learn: **Never use signed ints for sizes and indices. Use unsigned ints, or better, `std::size_t`**. This kind of implementation of `memcpy()` is the classic example of a signed int based attack. – Manu343726 Jan 19 '14 at 13:16
  • 2
    `size /= 4` is still dangerous. Hint: `sizeof` is your friend. – Bob Jarvis - Слава Україні Jan 19 '14 at 13:16
  • @BobJarvis This is a memcpy function how could i do sizeof?, there is no info of type. – this Jan 19 '14 at 13:17
  • 3
    Your implementation is using `uint32_t`. How big is a `uint32_t`? I don't know - I know what I might GUESS it would be, but I don't KNOW - and I *absolutely* don't know on any and all platforms. Try `size /= sizeof(uint32_t)`. – Bob Jarvis - Слава Україні Jan 19 '14 at 13:20
  • So are you saying that here is no strict aliasing issue in the first example? – this Jan 19 '14 at 13:20
  • 1
    @PaulR: the OP said elsewhere that this is a function designed specifically for data that has a size as a multiple of 4. – abiessu Jan 19 '14 at 13:21
  • 2
    @BobJarvis uint32_t is well defined and its size is 4 on every platform. I mean really.. – this Jan 19 '14 at 13:21
  • 2
    @self. I don't think anyone is saying that there is no strict aliasing issue. Reading e.g. 6.5:6 and 6.5:7 in the C99 standard, your code definitely breaks strict aliasing when the function is called, unless it is called to copy arrays of `uint32_t`. – Pascal Cuoq Jan 19 '14 at 13:21
  • 3
    A proper implementation has to deal with the fact that the pointers in question (both source and destination) may be unaligned relative to whatever boundaries may be important for a particular architecture. I know this is just an exercise, but I encourage you to sit down and handle all the edge conditions. That's how one learns. – Bob Jarvis - Слава Україні Jan 19 '14 at 13:22
  • 4
    `sizeof(uint32_t)` is *usually* 4, but it can be less than this on some platforms where `CHAR_BIT > 8`. – Paul R Jan 19 '14 at 13:23
  • 2
    Possible duplicate [Type punning with void * without breaking the strict aliasing rule in C99](http://stackoverflow.com/questions/15745030/type-punning-with-void-without-breaking-the-strict-aliasing-rule-in-c99) [C memory allocator and strict aliasing](http://stackoverflow.com/questions/7687082/c-memory-allocator-and-strict-aliasing) – lukes Jan 19 '14 at 13:34
  • 4
    @self.: `uint32_t` is defined to be 32 bits, not 4 bytes. There's no requirement for a byte to be 8 bits, and there are plenty of platforms where it isn't. – Mike Seymour Jan 19 '14 at 13:48
  • @MikeSeymour Thank you. Can you name a few? – this Jan 19 '14 at 13:48
  • 3
    @self.: DSP style architectures (e.g. Motorola 56k, TI TMS) are the most common these days - they typically have a word size large enough for the data they're designed for (16,24,32,... bits) with no support for smaller addressable units. In the past, mainframe and supercomputer architectures (e.g. PDP-11, Cray) had similar constraints; these are uncommon today, but I've no doubt there are still some specialist supercomputers around. And there's no way to tell whether future architectures will maintain the quirks of today's popular processors. – Mike Seymour Jan 19 '14 at 13:58

1 Answers1

17

The way to implement memcpy using more than single-byte copies is to use non-standard C.

Standard C does not support implementing memcpy using other than character types.

Quality C implementations provide an optimized memcpy implementation that performs efficient copying using more than single-byte copies, but they use implementation-specific code to do so. They may do this by compiling the memcpy implementation with a switch such as -fnostrict-aliasing to tell the compiler the aliasing rules will be violated in the code, by relying on known features of the specific C implementation to ensure the code will work (if you write the compiler, you can design it so that your implementation of memcpy works), or by writing memcpy in assembly language.

Additionally, C implementations may optimize memcpy calls where they appear in source code, replacing them by direct instructions to perform the operation or by simply changing the internal semantics of the program. (E.g., if you copy a into b, the compiler might not perform a copy at all but might simply load from a where subsequent code accesses b.)

To implement your own specialized copy operation while violating aliasing rules, compile it with -fnostrict-aliasing, if you are using GCC or Clang. If you are using another compiler, check its documentation for an option to disable the aliasing rules. (Note: Apple’s GCC, which I use, disables strict aliasing by default and accepts -fstrict-aliasing but not -fnostrict-aliasing. I am presuming non-Apple GCC accepts -fnostrict-aliasing.)

If you are using a good C implementation, you may find that your four-byte-copy implementation of memcpy4 does not perform as well as the native memcpy, depending on circumstances.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312