Is it guaranteed to be safe to perform memcpy(0,0,0)?

CMemcpyLanguage LawyerNull Pointer

C Problem Overview


I am not so well-versed in the C standard, so please bear with me.

I would like to know if it is guaranteed, by the standard, that memcpy(0,0,0) is safe.

The only restriction I could find is that if the memory regions overlap, then the behavior is undefined...

But can we consider that the memory regions overlap here ?

C Solutions


Solution 1 - C

I have a draft version of the C standard (ISO/IEC 9899:1999), and it has some fun things to say about that call. For starters, it mentions (§7.21.1/2) in regards to memcpy that

> Where an argument declared as size_t n specifies the length of the array for a function, n can have the value zero on a call to that function. Unless explicitly stated otherwise in the description of a particular function in this subclause, pointer arguments on such a call shall still have valid values, as described in 7.1.4. On such a call, a function that locates a character finds no occurrence, a function that compares two character sequences returns zero, and a function that copies characters copies zero characters.

The reference indicated here points to this:

> If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, or a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) or a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined.

So it looks like according to the C spec, calling

memcpy(0, 0, 0)

results in undefined behavior, because null pointers are considered "invalid values."

That said, I would be utterly astonished if any actual implementation of memcpy broke if you did this, since most of the intuitive implementations I can think of would do nothing at all if you said to copy zero bytes.

Solution 2 - C

Just for fun, the release-notes for gcc-4.9 indicate that its optimizer makes use of these rules, and for example can remove the conditional in

int copy (int* dest, int* src, size_t nbytes) {
    memmove (dest, src, nbytes);
    if (src != NULL)
        return *src;
    return 0;
}

which then gives unexpected results when copy(0,0,0) is called (see https://gcc.gnu.org/gcc-4.9/porting_to.html).

I am somewhat ambivalent about the gcc-4.9 behaviour; the behaviour might be standards compliant, but being able to call memmove(0,0,0) is sometimes a useful extension to those standards.

Solution 3 - C

You can also consider this usage of memmove seen in Git 2.14.x (Q3 2017)

See commit 168e635 (16 Jul 2017), and commit 1773664, commit f331ab9, commit 5783980 (15 Jul 2017) by René Scharfe (rscharfe).
(Merged by Junio C Hamano -- gitster -- in commit 32f9025, 11 Aug 2017)

It uses an helper macro MOVE_ARRAY which calculates the size based on the specified number of elements for us and supports NULL pointers when that number is zero.
Raw memmove(3) calls with NULL can cause the compiler to (over-eagerly) optimize out later NULL checks.

> MOVE_ARRAY adds a safe and convenient helper for moving potentially overlapping ranges of array entries.
It infers the element size, multiplies automatically and safely to get the size in bytes, does a basic type safety check by comparing element sizes and unlike memmove(3) it supports NULL pointers iff 0 elements are to be moved.

#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
	BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
	if (n)
		memmove(dst, src, st_mult(size, n));
}

Examples:

- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);

It uses the macro BUILD_ASSERT_OR_ZERO which asserts a build-time dependency, as an expression (with @cond being the compile-time condition which must be true).
The compilation will fail if the condition isn't true, or can't be evaluated by the compiler.

#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)

Example:

#define foo_to_char(foo)				\
	 ((char *)(foo)						\
	  + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))

Solution 4 - C

No, memcpy(0,0,0) is not safe. The standard library will likely not fail on that call. However in a testing envirenment, some extra code might be present in memcpy() to detect buffer overruns and other problems. And how that special version of memcpy() reacts to NULL pointers is, well, undefined.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatthieu M.View Question on Stackoverflow
Solution 1 - CtemplatetypedefView Answer on Stackoverflow
Solution 2 - Cuser1998586View Answer on Stackoverflow
Solution 3 - CVonCView Answer on Stackoverflow
Solution 4 - CKai PetzkeView Answer on Stackoverflow