Is copying 2D arrays with “memcpy” technically undefined behaviour?

std::memcpy(arr_copy, arr, sizeof arr); (your example) is well-defined.

std::memcpy(arr_copy, arr[0], sizeof arr);, on the other hand, causes undefined behavior (at least in C++; not entirely sure about C).


Multidimensional arrays are 1D arrays of arrays. As far as I know, they don’t get much (if any) special treatment compared to true 1D arrays (i.e. arrays with elements of non-array type).

Consider an example with a 1D array:

int a[3] = {1,2,3}, b[3];
std::memcpy(b, a, sizeof(int) * 3);

This is obviously legal.1

Notice that memcpy receives a pointer to the first element of the array, and can access other elements.

The element type doesn’t affect the validity of this example. If you use a 2D array, the element type becomes int[N] rather than int, but the validity is not affected.

Now, consider a different example:

int a[2][2] = {{1,2},{3,4}}, b[4];
std::memcpy(b, a[0], sizeof(int) * 4);
//             ^~~~

This one causes UB2, because since memcpy is given a pointer to the first element of a[0], it can only access the elements of a[0] (a[0][i]), and not a[j][i].

But, if you want my opinion, this is a “tame” kind of UB, likely to not cause problems in practice (but, as always, UB should be avoided if possible).



1 The C++ standard doesn’t explain memcpy, and instead refers to the C standard. The C standard uses somewhat sloppy wording:

C11 (N1570) [7.24.2.1]/2

The memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1.

A pointer to the first (or any) element of an array points only to that element, not to the entire array, even though the entire array is reachable through said pointer. Thus, if interpreted literally, it appears that @LanguageLawyer is right: if you give memcpy a pointer to an array element, you’re only allowed to copy that single element, and not the successive elements.

This interpretation contradicts the common sense, and most probably wasn’t intended.

E.g. consider the example in [basic.types.general]/2, which applies memcpy to an array using a pointer to the first element: (even though examples are non-normative)

constexpr std::size_t N = sizeof(T);
char buf[N];
T obj;
std::memcpy(buf, &obj, N);
std::memcpy(&obj, buf, N);

2 This is moot, because of the problematic wording for memcpy described above.

I’m not entirely sure about C, but for C++, there are strong hints that this is UB.

Firstly, consider a similar example that uses std::copy_n, attempting to perform an element-wise copy rather than a byte-wise one:

#include <algorithm>

consteval void foo()
{
    int a[2][2] = {{1,2},{3,4}}, b[2][2] = {{1,2},{3,4}};
    std::copy_n(a[0], 4, b[0]);
}

int main() {foo();} 

Running functions at compile-time catches most form of UB (it makes the code ill-formed), and indeed compiling this snippet gives:

error: call to consteval function 'foo' is not a constant expression
note: cannot refer to element 4 of array of 2 elements in a constant expression

The situation with memcpy is less certain, because it performs a byte-wise copy. This whole topic seems appears to be vague and underspecified.

Consider the wording for std::launder:

[ptr.launder]/4

A byte of storage b is reachable through a pointer value that points to an object Y if there is an object Z, pointer-interconvertible with Y, such that b is within the storage occupied by Z, or the immediately-enclosing array object if Z is an array element.

In other words, given a pointer to an array element, all elements of the said array are reachable through that pointer (non-recursively, i.e. through &a[0][0] only a[0][i] are reachable).

Formally, this definition is only used to describe std::launder (the fact that it can’t expand the reachable region of the pointer given to it). But the implication seems to be that this definition summarizes reachability rules described by other parts of the standard ([static.cast]/13, notice that reinterpret_cast is defined through the same wording; also [basic.compound]/4).

It’s not entirely clear if said rules apply to memcpy, but they should. Because otherwise, the programmer would be able to disregard reachability using library functions, which would make the concept of reachability mostly useless.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)