使用memcpy复制二维数组在C/C++中是否属于技术上的未定义行为?
近期相关问题的评论区产生了一个有趣的讨论:尽管原问题涉及的是C语言,但讨论已经延伸到*C++*标准的规定范畴,即使用std::memcpy这类函数访问多维数组元素时,什么情况会构成未定义行为。
首先,以下是原问题代码转换为C++版本的实现,已尽可能使用const修饰:
#include <iostream> #include <cstring> void print(const int arr[][3], int n) { for (int r = 0; r < 3; ++r) { for (int c = 0; c < n; ++c) { std::cout << arr[r][c] << " "; } std::cout << std::endl; } } int main() { const int arr[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; int arr_copy[3][3]; print(arr, 3); std::memcpy(arr_copy, arr, sizeof arr); print(arr_copy, 3); return 0; }
争议点出现在std::memcpy的调用上:arr参数会(通过数组退化)得到指向首个int[3]子数组的指针,因此以Ted Lyngmo为代表的一方观点认为,当memcpy函数访问超出该子数组第三个元素的数据时,形式上就属于未定义行为(目标参数arr_copy也存在同样问题)。
而包括mediocrevegetable1在内的反对方则持如下逻辑:根据定义,每个二维数组的内存空间都是连续的,且传入memcpy的参数仅为指向对应位置的void*指针(第三个size参数也合法),因此这里不存在未定义行为。
以下是本次争议相关的核心评论汇总:
我不认为这里存在越界访问。就像
memcpy可以正常复制int类型数组一样,它也适用于int [3]类型的数组,二者的内存都是连续的(不过我不能百分百确定)。—— mediocrevegetable1越界访问出现在你复制
arr[0][3]的第一个字节时。我从没见过这种写法实际运行出错,但在C++中这确实属于UB。—— Ted Lyngmo但
memcpy函数/调用本身并没有做任何数组索引操作,它只是接收两个void*指针,然后将内存从一个地址复制到另一个地址。—— Adrian Mole我不确定这在C中是否有影响,但在C中确实有问题。你得到的是指向第一个
int[3]的指针,任何超出其范围的访问都属于UB。*我在C标准中没有找到任何相关的例外规定。*—— Ted Lyngmo我不认为
arr[0][3]的说法成立。按照这个逻辑,用memcpy复制int数组的第二个int元素也会是UB了。int [3]只是arr的元素类型,arr整体的字节边界应该是sizeof (int [3]) * 3。不过我可能漏看了什么:/ —— mediocrevegetable1
有没有C语言律师可以解决这个问题?最好能附上C标准的相关引用佐证。此外,来自C标准的相关引用也会很有帮助——尤其是如果两种语言的标准在这一点上存在差异的话,因此该问题也添加了C标签。
内容的提问来源于stack exchange,提问作者Adrian Mole

