Is memcpy with len=0 a NOP?

N

Noob

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

Ben Bacarisse

Noob said:
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>
 
N

Noob

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


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;
}
 
F

Francois Grieu

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
 
N

Noob

Francois said:
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)
 
J

J. J. Farrell

Noob said:
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.
 
B

Ben Bacarisse

J. J. Farrell said:
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!
 
D

Dr Nick

J. J. Farrell said:
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.

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

Jorgen Grahn

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
 
B

Ben Bacarisse

Dr Nick said:
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 */
 
M

Marcin Grzegorczyk

Noob said:
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.
 
D

Donkey Hottie

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

Keith Thompson

Donkey Hottie said:
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.
 
F

Francois Grieu

Le 27/01/2011 21:24, Jorgen Grahn a écrit :
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
 
J

Jorgen Grahn

Le 27/01/2011 21:24, Jorgen Grahn a écrit :

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

Eric Sosman

[... 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.
 
F

Francois Grieu

[... 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 said:
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].
See also Jorgen Grahn's response.

Yes. Again I often do just that.


Francois Grieu
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top