memcpy参数是否需使用数组指针而非首元素指针的标准合规性疑问及相关标准缺陷探讨
作为天天跟标准文本“死磕细节”的语言律师,咱们今天就从抽象机器行为和标准措辞的角度来拆解这个问题——先说好,咱们完全不管实际编译器会不会正常运行这段代码,只抠标准里的字儿。
首先先把标准依据摆出来:
C++23(其实更早版本的措辞也没有变化)明确规定,
头文件的内容与C标准库的<string.h>完全一致;而C23标准的7.26.2.1条款对memcpy的定义为:memcpy函数从s2指向的对象复制n个字符到s1指向的对象。
接下来看C++标准自己给出的这段示例代码:
constexpr std::size_t N = sizeof(T); char buf[N]; T obj; std::memcpy(buf, &obj, N); std::memcpy(&obj, buf, N);
这里的微妙之处在于:buf是数组名,经过数组到指针的隐式转换(decay)后,传给memcpy的指针实际上指向的是buf的第一个char元素,而非整个char数组对象。但按照memcpy的标准定义,它操作的应该是指针“指向的对象”——那从字面意思抠,这里memcpy复制的就应该是单个char元素,而非我们实际需要的整个N字节的连续存储?
那问题就来了:是不是必须换成&buf(指向整个数组的指针)才完全合规、彻底避免未定义行为?这会不会是标准措辞上的一个缺陷?
咱们再往深了抠:memcpy的参数类型是void*,不管传buf还是&buf,都会被隐式转换为void*,但关键在于标准里说的“指向的对象”到底指什么。当我们传buf时,这个指针的原始类型是char*,指向的是单个char对象;而传&buf时,原始类型是char(*)[N],指向的是整个char数组对象。
从抽象机器的角度看,标准并没有明确说明:当指针指向数组的第一个元素时,memcpy可以将后续连续的元素序列视为一个整体操作对象。虽然在实际编程中大家都默认这么用,但从语言律师的严格视角来说,这就存在歧义——标准的措辞限定了memcpy操作的是“指针指向的对象”,但我们实际想操作的是数组的所有元素,而不是第一个元素。
更耐人寻味的是,这段“有歧义”的代码居然是C++标准自己给出的示例。这就说明:要么是标准的措辞存在缺陷(没有明确数组decay后的指针可以代表整个数组作为memcpy的操作对象),要么是我们对“对象”的定义理解过于狭隘?
其实核心矛盾在于:C和C++标准在设计memcpy时,默认了它是操作一段连续存储的起始位置,而不管这个指针的原始指向是单个元素还是整个数组,但标准的措辞却严格限定为“指向的对象”,这就造成了字面措辞与实际意图的脱节。
最后总结一下:
从最严格的标准字面意义来讲,传buf(指向第一个元素的指针)确实不符合措辞要求,应该传&buf才是指向整个数组对象的正确指针;但标准自己的示例却使用了buf,这明显说明标准的措辞存在模糊或缺陷,没有明确衔接“指向数组首元素的指针”与“memcpy操作整个数组”之间的逻辑。
内容来源于stack exchange

