朱(Howard Chu)writes:
在最新的C规范中,不可能编写malloc或memcpy的“合法”实现。
这是对的吗?我的印象是,在过去,该标准(至少)的意图是可以实现以下目的:
void * memcpy(void * restrict destination, const void * restrict source, size_t nbytes)
{
size_t i;
unsigned char *dst = (unsigned char *) destination;
const unsigned char *src = (const unsigned char *) source;
for (i = 0; i < nbytes; i++)
dst[i] = src[i];
return destination;
}
这里违反了最新C标准的哪些规则?还是memcpy
的规范的哪一部分没有被该代码正确实现?
答案 0 :(得分:1)
对于malloc
函数,第6.5§6段清楚地表明,不可能编写一致且可移植的C实现:
访问其存储值的对象的有效类型是已声明的对象类型。 对象,如果有的话(87) ...
(非规范性)注释87说:
已分配的对象没有声明的类型。
声明没有声明类型的对象的唯一方法是...通过分配函数返回该对象!因此,在分配函数内部,您必须具有某物,这是标准所不允许的,以设置没有声明类型的内存区域。
在常见的实现中,标准库malloc和free实际上是在C中实现的,但是系统对此有所了解,并假设malloc
中提供的字符数组没有声明的类型。句号。
但是同一段的其余部分说明,编写memcpy
实现并没有真正的问题(强调我的想法):
...如果通过一个值将值存储到没有声明类型的对象中 具有非字符类型的左值,则左值的类型变为 该访问和不修改的后续访问的对象的有效类型 储值。如果将值复制到没有声明类型的对象中,则使用 memcpy或memmove或复制为字符类型数组,然后是有效类型 修改后的对象的访问权限以及不修改 value是从中复制值的对象的有效类型(如果有)。对于 对没有声明类型的对象的所有其他访问,则该对象的有效类型为 只是用于访问的左值的类型。
假设您将对象复制为字符类型的数组,这是严格的别名规则所允许的特殊访问,实现memcpy
不会出现问题,并且您的代码是可能且有效的实现。 / p>
恕我直言,霍华德·朱(Howard Chu)的咆哮与旧商品memcpy
的用法有关,该用法不再有效(假设sizeof(float) == sizeof(int)
):
float f = 1.0;
int i;
memcpy(&i, &f, sizeof(int)); // valid: copy at byte level, but the value of i is undefined
print("Repr of %f is %x\n", i, i); // UB: i cannot be accessed as a float
答案 1 :(得分:0)
TL; DR
只要memcpy
基于幼稚的逐个字符复制,就可以了。
尚未进行优化,无法移动可在单个指令中复制的最大对齐类型大小的块。后者是标准lib实现的方式。
所涉及的是这种情况:
void* my_int = malloc(sizeof *my_int);
int another_int = 1;
my_memcpy(my_int, &another_int, sizeof(int));
printf("%d", *(int*)my_int); // well-defined or strict aliasing violation?
说明:
my_int
的数据没有有效的类型。my_int
位置时,可能会担心由于unsigned char
使用的是强制有效类型变为my_memcpy
。int*
读取该内存位置时。我们会违反严格的别名吗?但是,这里的键是C17 6.5 / 6中指定的有效类型规则中的一个特殊例外,重点是我的:
如果将值复制到没有声明类型的对象,请使用
memcpy
或memmove
,或复制为字符类型数组,然后修改对象的有效类型访问和后续访问,这些访问不会修改 值是从中复制值的对象的有效类型(如果有)。
由于我们确实将数组复制为字符类型,因此my_int
指向的有效类型将成为复制值的对象another_int
的有效类型。
所以一切都很好。
此外,您restrict
对参数进行了限定,因此就不必像真实的memcpy
那样两个指针是否可能互为别名。
值得注意的是,在C99,C11和C17中,此规则保持不变。有人可能会说这是编译器供应商滥用的非常糟糕的规则,但这是另一回事。