Is memcpy with len=0 a NOP?

Discussion in 'C Programming' started by Noob, Jan 24, 2011.

  1. Noob

    Noob Guest

    Hello,

    Consider

    int i = 123;
    int j = 456;
    memcpy(&i, &j, 0);

    Is the call to memcpy with len==0 well-defined, and equivalent
    to a NOP, leaving the entire state machine unchanged?
    (i and j unchanged, but everything else too)

    I believe memcpy(foo, bar, 0) is indeed equivalent to a NOP.
    Please correct me if I'm wrong.

    My C89 draft only states

    4.11.2.1 The memcpy function

    Synopsis

    #include <string.h>
    void *memcpy(void *s1, const void *s2, size_t n);

    Description

    The memcpy function copies n characters from the object pointed to
    by s2 into the object pointed to by s1 . If copying takes place
    between objects that overlap, the behavior is undefined.

    Returns

    The memcpy function returns the value of s1 .

    Regards.
     
    Noob, Jan 24, 2011
    #1
    1. Advertising

  2. Noob <root@127.0.0.1> writes:

    > Consider
    >
    > int i = 123;
    > int j = 456;
    > memcpy(&i, &j, 0);
    >
    > Is the call to memcpy with len==0 well-defined, and equivalent
    > to a NOP, leaving the entire state machine unchanged?
    > (i and j unchanged, but everything else too)


    In this case, yes, it's a no-op.

    > I believe memcpy(foo, bar, 0) is indeed equivalent to a NOP.
    > Please correct me if I'm wrong.


    This is not quite a no-op because the call is undefined when either or
    both of the pointer arguments are null (see 7.1.4 p1 of C99). It's a
    no-op when both pointers are valid, and it is even a well-defined (as a
    no-op) when the pointers are the same (where a call with any non-zero
    value for the size would be undefined).

    I've come across a few (admittedly rare) cases where code would be
    simpler if the degenerate case of copying zero bytes to or from a null
    pointer were well-defined. I image there is a non-trivial amount of
    code "in the wild" that assumes it is well-defined.

    <snip>
    --
    Ben.
     
    Ben Bacarisse, Jan 24, 2011
    #2
    1. Advertising

  3. Noob

    Noob Guest

    Ben Bacarisse wrote:

    > Noob wrote:
    >
    >> Consider
    >>
    >> int i = 123;
    >> int j = 456;
    >> memcpy(&i,&j, 0);
    >>
    >> Is the call to memcpy with len==0 well-defined, and equivalent
    >> to a NOP, leaving the entire state machine unchanged?
    >> (i and j unchanged, but everything else too)

    >
    > In this case, yes, it's a no-op.
    >
    >> I believe memcpy(foo, bar, 0) is indeed equivalent to a NOP.
    >> Please correct me if I'm wrong.

    >
    > This is not quite a no-op because the call is undefined when either or
    > both of the pointer arguments are null (see 7.1.4 p1 of C99). It's a
    > no-op when both pointers are valid, and it is even a well-defined (as a
    > no-op) when the pointers are the same (where a call with any non-zero
    > value for the size would be undefined).
    >
    > I've come across a few (admittedly rare) cases where code would be
    > simpler if the degenerate case of copying zero bytes to or from a null
    > pointer were well-defined. I image there is a non-trivial amount of
    > code "in the wild" that assumes it is well-defined.


    The actual use-case is copying data to a "circular" buffer.

    unsigned bytes_until_wrap_around = dest_end - write_pointer;
    if (buflen < bytes_until_wrap_around)
    {
    /* COMMON CASE : NO WRAP-AROUND */
    memcpy(write_pointer, buf, buflen);
    write_pointer += buflen;
    }
    else
    {
    /* EXCEPTION : WRAP-AROUND */
    int len2 = buflen - bytes_until_wrap_around;
    memcpy(write_pointer, buf, bytes_until_wrap_around);
    memcpy(dest_base, buf + bytes_until_wrap_around, len2);
    write_pointer = dest_base + len2;
    }
     
    Noob, Jan 24, 2011
    #3
  4. On 24/01/2011 11:15, Noob wrote:
    > Is the call to memcpy with len==0 well-defined, and equivalent
    > to a NOP, leaving the entire state machine unchanged?


    I won't try to answer that based on the C standard,
    but here is a recent (2009-2010) anecdote.

    I was developing for an STM7-based embedded system.
    The recent, under-active-support C compiler had an
    "intrinsic" memcpy that generated tight code when
    the source and destination of memcpy were known at
    link time and the length fitted a byte.
    I found that hard way that this optimization was
    broken: when the length had the value 0 and was not
    a constant expression (e.g. was a byte variable),
    256 bytes got copied.

    Francois Grieu
     
    Francois Grieu, Jan 26, 2011
    #4
  5. Noob

    Noob Guest

    Francois Grieu wrote:

    > On 24/01/2011 11:15, Noob wrote:
    >
    >> Is the call to memcpy with len==0 well-defined, and equivalent
    >> to a NOP, leaving the entire state machine unchanged?

    >
    > I won't try to answer that based on the C standard,
    > but here is a recent (2009-2010) anecdote.
    >
    > I was developing for an STM7-based embedded system.
    > The recent, under-active-support C compiler had an
    > "intrinsic" memcpy that generated tight code when
    > the source and destination of memcpy were known at
    > link time and the length fitted a byte.
    > I found that hard way that this optimization was
    > broken: when the length had the value 0 and was not
    > a constant expression (e.g. was a byte variable),
    > 256 bytes got copied.


    My target is an SH-4 from ST, but (luckily) the tool chain
    is GNU-based (binutils 2.19.1, GCC 4.3.4, newlib 1.17.0)
     
    Noob, Jan 26, 2011
    #5
  6. Noob wrote:
    >
    > Consider
    >
    > int i = 123;
    > int j = 456;
    > memcpy(&i, &j, 0);
    >
    > Is the call to memcpy with len==0 well-defined, and equivalent
    > to a NOP, leaving the entire state machine unchanged?
    > (i and j unchanged, but everything else too)
    >
    > I believe memcpy(foo, bar, 0) is indeed equivalent to a NOP.
    > Please correct me if I'm wrong.
    >
    > My C89 draft only states


    Why do you say "only" there? The text below fully defines how the
    function must behave when the length is 0, in just the same way as it
    defines how it must behave when the length is 42.

    > 4.11.2.1 The memcpy function
    >
    > Synopsis
    >
    > #include <string.h>
    > void *memcpy(void *s1, const void *s2, size_t n);
    >
    > Description
    >
    > The memcpy function copies n characters from the object pointed to
    > by s2 into the object pointed to by s1 . If copying takes place
    > between objects that overlap, the behavior is undefined.
    >
    > Returns
    >
    > The memcpy function returns the value of s1 .
     
    J. J. Farrell, Jan 27, 2011
    #6
  7. "J. J. Farrell" <> writes:

    > Noob wrote:
    >>
    >> Consider
    >>
    >> int i = 123;
    >> int j = 456;
    >> memcpy(&i, &j, 0);
    >>
    >> Is the call to memcpy with len==0 well-defined, and equivalent
    >> to a NOP, leaving the entire state machine unchanged?
    >> (i and j unchanged, but everything else too)
    >>
    >> I believe memcpy(foo, bar, 0) is indeed equivalent to a NOP.
    >> Please correct me if I'm wrong.
    >>
    >> My C89 draft only states

    >
    > Why do you say "only" there? The text below fully defines how the
    > function must behave when the length is 0, in just the same way as it
    > defines how it must behave when the length is 42.


    The "only" struck me as odd too, but at the same time the text below
    does not fully define what memcpy does when the length is 0. In
    particular, it does not fully answer the question of whether memcpy(foo,
    bar, 0) is a NOP or not so maybe that is the source of the "only".

    There is other text elsewhere that defines what happens when either
    pointer argument is a null pointer, and the result is pretty much the
    opposite of a NOP: it's an "anything" OP!

    >> 4.11.2.1 The memcpy function
    >>
    >> Synopsis
    >>
    >> #include <string.h>
    >> void *memcpy(void *s1, const void *s2, size_t n);
    >>
    >> Description
    >>
    >> The memcpy function copies n characters from the object pointed to
    >> by s2 into the object pointed to by s1 . If copying takes place
    >> between objects that overlap, the behavior is undefined.
    >>
    >> Returns
    >>
    >> The memcpy function returns the value of s1 .


    --
    Ben.
     
    Ben Bacarisse, Jan 27, 2011
    #7
  8. Noob

    Dr Nick Guest

    "J. J. Farrell" <> writes:

    > Noob wrote:
    >>
    >> Consider
    >>
    >> int i = 123;
    >> int j = 456;
    >> memcpy(&i, &j, 0);
    >>
    >> Is the call to memcpy with len==0 well-defined, and equivalent
    >> to a NOP, leaving the entire state machine unchanged?
    >> (i and j unchanged, but everything else too)
    >>
    >> I believe memcpy(foo, bar, 0) is indeed equivalent to a NOP.
    >> Please correct me if I'm wrong.
    >>
    >> My C89 draft only states

    >
    > Why do you say "only" there? The text below fully defines how the
    > function must behave when the length is 0, in just the same way as it
    > defines how it must behave when the length is 42.
    >
    >> 4.11.2.1 The memcpy function
    >>
    >> Synopsis
    >>
    >> #include <string.h>
    >> void *memcpy(void *s1, const void *s2, size_t n);
    >>
    >> Description
    >>
    >> The memcpy function copies n characters from the object pointed to
    >> by s2 into the object pointed to by s1 . If copying takes place
    >> between objects that overlap, the behavior is undefined.
    >>
    >> Returns
    >>
    >> The memcpy function returns the value of s1 .


    I don't think that text is entirely clear. If n is zero we know from
    the first sentence that zero characters are copied. But it's far from
    clear to me whether if zero bytes have been copied, "copying" has "taken
    place", so I remain unsure from the quoted text whether memcpy(x,x,0) is
    defined as doing nothing or undefined.
    --
    Online waterways route planner | http://canalplan.eu
    Plan trips, see photos, check facilities | http://canalplan.org.uk
     
    Dr Nick, Jan 27, 2011
    #8
  9. Noob

    Jorgen Grahn Guest

    On Wed, 2011-01-26, Francois Grieu wrote:
    > On 24/01/2011 11:15, Noob wrote:
    >> Is the call to memcpy with len==0 well-defined, and equivalent
    >> to a NOP, leaving the entire state machine unchanged?

    >
    > I won't try to answer that based on the C standard,
    > but here is a recent (2009-2010) anecdote.
    >
    > I was developing for an STM7-based embedded system.
    > The recent, under-active-support C compiler had an
    > "intrinsic" memcpy that generated tight code when
    > the source and destination of memcpy were known at
    > link time and the length fitted a byte.
    > I found that hard way that this optimization was
    > broken: when the length had the value 0 and was not
    > a constant expression (e.g. was a byte variable),
    > 256 bytes got copied.


    Your compiler was broken. With all due respect, I see no other lesson
    in that. Surely lots of real-life code expects memcpy(foo, bar, n),
    with n evaluating to 0, to work as expected.

    /Jorgen

    --
    // Jorgen Grahn <grahn@ Oo o. . .
    \X/ snipabacken.se> O o .
     
    Jorgen Grahn, Jan 27, 2011
    #9
  10. Dr Nick <> writes:
    >> Noob wrote:

    <snip>
    >>> 4.11.2.1 The memcpy function
    >>>
    >>> Synopsis
    >>>
    >>> #include <string.h>
    >>> void *memcpy(void *s1, const void *s2, size_t n);
    >>>
    >>> Description
    >>>
    >>> The memcpy function copies n characters from the object pointed to
    >>> by s2 into the object pointed to by s1 . If copying takes place
    >>> between objects that overlap, the behavior is undefined.
    >>>
    >>> Returns
    >>>
    >>> The memcpy function returns the value of s1 .

    >
    > I don't think that text is entirely clear. If n is zero we know from
    > the first sentence that zero characters are copied. But it's far from
    > clear to me whether if zero bytes have been copied, "copying" has "taken
    > place", so I remain unsure from the quoted text whether memcpy(x,x,0) is
    > defined as doing nothing or undefined.


    I don't think copying zero bytes can constitute copying taking place
    between overlapping objects. For one thing, zero byte objects can't
    overlap.

    Another way to think about it that memcpy(x,x,0) is just the last step
    in a sequence of valid copies:

    memcpy(x, x+n, n); /* ok (space permitting) */
    ...
    memcpy(x, x+2, 2); /* ok */
    memcpy(x, x+1, 1); /* ok */
    memcpy(x, x+0, 0); /* ok */

    --
    Ben.
     
    Ben Bacarisse, Jan 28, 2011
    #10
  11. Noob wrote:
    > Consider
    >
    > int i = 123;
    > int j = 456;
    > memcpy(&i, &j, 0);
    >
    > Is the call to memcpy with len==0 well-defined, and equivalent
    > to a NOP, leaving the entire state machine unchanged?


    In case of C99, the answer is clearly yes. 7.21.1p2 explicitly
    addresses the case of n==0 for all <string.h> functions declared with a
    `size_t n` parameter.
    --
    Marcin Grzegorczyk
     
    Marcin Grzegorczyk, Jan 28, 2011
    #11
  12. On 28.1.2011 20:05, Marcin Grzegorczyk wrote:
    > Noob wrote:
    >> Consider
    >>
    >> int i = 123;
    >> int j = 456;
    >> memcpy(&i, &j, 0);
    >>
    >> Is the call to memcpy with len==0 well-defined, and equivalent
    >> to a NOP, leaving the entire state machine unchanged?

    >
    > In case of C99, the answer is clearly yes. 7.21.1p2 explicitly
    > addresses the case of n==0 for all <string.h> functions declared with a
    > `size_t n` parameter.


    Why NOP?

    If not

    PUSH ...
    PUSH ...
    PUSH ...
    CALL ...

    whatever, but why NOP?

    The compiler could just drop it.

    --

    You will obey or molten silver will be poured into your ears.
     
    Donkey Hottie, Feb 1, 2011
    #12
  13. Donkey Hottie <> writes:
    > On 28.1.2011 20:05, Marcin Grzegorczyk wrote:
    >> Noob wrote:
    >>> Consider
    >>>
    >>> int i = 123;
    >>> int j = 456;
    >>> memcpy(&i, &j, 0);
    >>>
    >>> Is the call to memcpy with len==0 well-defined, and equivalent
    >>> to a NOP, leaving the entire state machine unchanged?

    >>
    >> In case of C99, the answer is clearly yes. 7.21.1p2 explicitly
    >> addresses the case of n==0 for all <string.h> functions declared with a
    >> `size_t n` parameter.

    >
    > Why NOP?
    >
    > If not
    >
    > PUSH ...
    > PUSH ...
    > PUSH ...
    > CALL ...
    >
    > whatever, but why NOP?


    NOP in this context doesn't refer to a machine instruction of that
    name. It just means "no operation". A call to memcpy() with 0 for the
    third argument (and valid pointers for the first two) has no effect
    (except possibly some amount of time).

    > The compiler could just drop it.


    That's exactly the point.

    --
    Keith Thompson (The_Other_Keith) <http://www.ghoti.net/~kst>
    Nokia
    "We must do something. This is something. Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"
     
    Keith Thompson, Feb 1, 2011
    #13
  14. Le 27/01/2011 21:24, Jorgen Grahn a écrit :
    > On Wed, 2011-01-26, Francois Grieu wrote:
    >> On 24/01/2011 11:15, Noob wrote:
    >>> Is the call to memcpy with len==0 well-defined, and equivalent
    >>> to a NOP, leaving the entire state machine unchanged?

    >>
    >> I won't try to answer that based on the C standard,
    >> but here is a recent (2009-2010) anecdote.
    >>
    >> I was developing for an STM7-based embedded system.
    >> The recent, under-active-support C compiler had an
    >> "intrinsic" memcpy that generated tight code when
    >> the source and destination of memcpy were known at
    >> link time and the length fitted a byte.
    >> I found that hard way that this optimization was
    >> broken: when the length had the value 0 and was not
    >> a constant expression (e.g. was a byte variable),
    >> 256 bytes got copied.

    >
    > Your compiler was broken.


    Yes.

    > With all due respect, I see no other lesson in that.


    That compilers ARE broken in corner cases is a lesson.

    > Surely lots of real-life code expects memcpy(foo, bar, n),
    > with n evaluating to 0, to work as expected.


    And I'll now write
    if (n>0) memcpy(foo, bar, n)
    when I want my code to run on more compilers.

    Francois Grieu
     
    Francois Grieu, Feb 2, 2011
    #14
  15. Noob

    Jorgen Grahn Guest

    On Wed, 2011-02-02, Francois Grieu wrote:
    > Le 27/01/2011 21:24, Jorgen Grahn a écrit :
    >> On Wed, 2011-01-26, Francois Grieu wrote:
    >>> On 24/01/2011 11:15, Noob wrote:
    >>>> Is the call to memcpy with len==0 well-defined, and equivalent
    >>>> to a NOP, leaving the entire state machine unchanged?
    >>>
    >>> I won't try to answer that based on the C standard,
    >>> but here is a recent (2009-2010) anecdote.
    >>>
    >>> I was developing for an STM7-based embedded system.
    >>> The recent, under-active-support C compiler had an
    >>> "intrinsic" memcpy that generated tight code when
    >>> the source and destination of memcpy were known at
    >>> link time and the length fitted a byte.
    >>> I found that hard way that this optimization was
    >>> broken: when the length had the value 0 and was not
    >>> a constant expression (e.g. was a byte variable),
    >>> 256 bytes got copied.

    >>
    >> Your compiler was broken.

    >
    > Yes.
    >
    >> With all due respect, I see no other lesson in that.

    >
    > That compilers ARE broken in corner cases is a lesson.


    Sure, I don't disagree with that -- except maybe that you classify
    this as a corner case. There is no doubt that memcpy() with a zero
    length should work, and that it's used in lots of real, working code.
    Probably, your standard library uses it too.

    >> Surely lots of real-life code expects memcpy(foo, bar, n),
    >> with n evaluating to 0, to work as expected.

    >
    > And I'll now write
    > if (n>0) memcpy(foo, bar, n)
    > when I want my code to run on more compilers.


    That will look very strange to your readers -- people who
    don't use the broken compiler, or who read the code ten
    years after the bug was fixed.

    I think I would reason like this:
    - Does the vendor seem responsive, e.g. promise a fix within
    a week or so? A broken memcpy() is a serious fault.
    YES => wait for the fix
    NO => write a replacement implementation of memcpy, document
    exactly why it's needed, and
    #ifdef HAVE_COMPILER_BUG_4711
    #define memcpy my_memcpy
    #define bcopy ... etc if needed
    #endif
    Or disable the builtin memcpy(), if the compiler
    supports that (gcc does that with -fno-builtins).
    NO => Also make a plan for switching to a serious compiler
    vendor, and plan for more problems like this one.

    /Jorgen

    --
    // Jorgen Grahn <grahn@ Oo o. . .
    \X/ snipabacken.se> O o .
     
    Jorgen Grahn, Feb 2, 2011
    #15
  16. Noob

    Eric Sosman Guest

    On 2/2/2011 5:53 AM, Francois Grieu wrote:
    > [... concerning a broken memcpy() implementation ...]
    >
    > And I'll now write
    > if (n>0) memcpy(foo, bar, n)
    > when I want my code to run on more compilers.


    I recall an implementation where `free(NULL)' was a rough
    equivalent for `abort()', but I don't write `if (ptr) free(ptr);'
    as a matter of routine; do you?

    I recall an implementation whose strlen() was a performance
    pig, but I do not write `ptr[0] ? strlen(ptr) : 0' as a matter
    of routine; do you?

    When it's a matter of getting around a bug in Frobozz Magic C
    in the absence of a fix from Frobozz, I'll use whatever hack I
    must (and so should you), but I don't think it makes sense to
    adopt those hacks into your normal coding practices. Use the
    hack when you must, but only when you must. Otherwise, you'll
    spend your time writing fixes for bugs that don't even exist
    (and you may well introduce new ones).

    See also Jorgen Grahn's response.

    --
    Eric Sosman
    lid
     
    Eric Sosman, Feb 2, 2011
    #16
  17. On 03/02/2011 00:32, Eric Sosman wrote:
    > On 2/2/2011 5:53 AM, Francois Grieu wrote:
    >> [... concerning a broken memcpy() implementation ...]
    >>
    >> And I'll now write
    >> if (n>0) memcpy(foo, bar, n)
    >> when I want my code to run on more compilers.


    Notice that I did not write: in all my code. I have a
    category of programs/libraries that I want to be able to
    hand to a customer/coworker, in source form, and be
    reasonably sure that it will produce the right result
    for them as supplied. That's when I go to such extremes.


    > I recall an implementation where `free(NULL)' was a rough
    > equivalent for `abort()', but I don't write `if (ptr) free(ptr);'
    > as a matter of routine; do you?


    I recall doing that a long time ago, but admittedly it was
    because I did not yet know that free(NULL) should be OK.

    > I recall an implementation whose strlen() was a performance
    > pig, but I do not write `ptr[0] ? strlen(ptr) : 0' as a matter
    > of routine; do you?


    No. I typically maintain a variable with the current length of a
    string, and sometime compute the string's length using my own
    code, especially when it is the only dependency to <string.h>

    > When it's a matter of getting around a bug in Frobozz Magic C
    > in the absence of a fix from Frobozz, I'll use whatever hack I
    > must (and so should you), but I don't think it makes sense to
    > adopt those hacks into your normal coding practices. Use the
    > hack when you must, but only when you must. Otherwise, you'll
    > spend your time writing fixes for bugs that don't even exist
    > (and you may well introduce new ones).


    For code targetting a particular platform, I do just that,
    including hiding the workaround as a macro and making it
    conditional to compilation under FrobozzC, reporting the bug
    with a smallish reproducible example, and when it is fixed
    making the workaround specific to the broken version of
    FrobozzC (or leaving it only in my compiler test suite).
    In the last 3 years I reported 18 issues to tool vendor C
    and 9 to tool vendor K.
    That particular memcpy() issue was fixed less than 2 weeks
    following my report, which mentioned I had a workaround.
    For that other bug without a reasonable workaround, I got
    4 hours turnaround time:

    <OT>
    ] The statement
    ] j + 2;
    ] may be compiled to code that add 1 (rather than 2) to j, when
    ] the previous and next statement use the byte j.
    ]
    ] ; 50 if( j < 5 )
    ] ld a,_j
    ] cp a,#5
    ] jruge L13
    ] ; 52 j += 2; // compiler messes up here
    ] inc _j
    ] inc a
    ] ; 53 if( j < 5 )
    ] ld _j,a
    ] cp a,#5
    ]
    ] This occurs for many compiler settings, including the defaults
    ] for [my target].
    </OT>

    > See also Jorgen Grahn's response.


    Yes. Again I often do just that.


    Francois Grieu
     
    Francois Grieu, Feb 3, 2011
    #17
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Saurabh Aggrawal

    _asm NOP : undeclared identifier

    Saurabh Aggrawal, Dec 10, 2004, in forum: C++
    Replies:
    3
    Views:
    3,633
    Thomas Matthews
    Dec 10, 2004
  2. Tor Erik Soenvisen
    Replies:
    14
    Views:
    592
    Tim Roberts
    Nov 23, 2006
  3. Stef Mientki
    Replies:
    5
    Views:
    356
    Jussi Salmela
    Jan 6, 2007
  4. TefJlives

    NOP pointer

    TefJlives, Mar 12, 2007, in forum: C Programming
    Replies:
    11
    Views:
    627
    Christopher Benson-Manica
    Mar 13, 2007
  5. maestro
    Replies:
    1
    Views:
    324
    Chris
    Aug 11, 2008
Loading...

Share This Page