【发布时间】:2016-11-30 19:18:06
【问题描述】:
这是来自 C11 标准的引用:
6.5 表达式
...6 用于访问其存储值的对象的有效类型是对象的声明类型(如果有)。如果通过具有非字符类型类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该类型的后续访问储值。如果使用
memcpy或memmove将值复制到没有声明类型的对象中,或者复制为字符类型的数组,则该访问的修改对象的有效类型以及不修改的后续访问该值是从中复制该值的对象的有效类型(如果有的话)。对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。7 对象的存储值只能由具有以下类型之一的左值表达式访问:
——与对象的有效类型兼容的类型,
— 与对象的有效类型兼容的类型的限定版本,
— 对应于对象有效类型的有符号或无符号类型,
— 有符号或无符号类型,对应于对象有效类型的限定版本,
— 在其成员中包含上述类型之一的聚合或联合类型(递归地,包括子聚合或包含联合的成员),或
— 一种字符类型。
这是否意味着memcpy 不能以这种方式用于类型双关:
double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);
为什么它不会给出与以下相同的输出:
union { double d; uint64_t i; } u;
u.d = 1234.5678;
printf("the representation of %g is %08"PRIX64"\n", d, u.i);
如果我使用字符类型的memcpy 版本会怎样:
void *my_memcpy(void *dst, const void *src, size_t n) {
unsigned char *d = dst;
const unsigned char *s = src;
for (size_t i = 0; i < n; i++) { d[i] = s[i]; }
return dst;
}
编辑: EOF 评论说 第 6 段中关于 memcpy() 的部分不适用于这种情况,因为 uint64_t bits 具有声明的类型。 我同意,但不幸的是,这无助于回答 memcpy 是否可以用于类型双关语的问题,它只是使第 6 段与评估上述示例的有效性无关。
这里是使用memcpy 进行类型双关的另一种尝试,我相信第 6 段会介绍:
double d = 1234.5678;
void *p = malloc(sizeof(double));
if (p != NULL) {
uint64_t *pbits = memcpy(p, &d, sizeof(double));
uint64_t bits = *pbits;
printf("the representation of %g is %08"PRIX64"\n", d, bits);
}
假设sizeof(double) == sizeof(uint64_t),上述代码是否在第 6 段和第 7 段中定义了行为?
编辑:一些答案指出读取陷阱表示可能会导致未定义的行为。这不相关,因为 C 标准明确排除了这种可能性:
7.20.1.1 精确宽度整数类型
1 typedef 名称
intN_t指定宽度为 N、无填充位和二进制补码表示的有符号整数类型。因此,int8_t表示这样一个宽度正好为 8 位的有符号整数类型。2 typedef 名称
uintN_t指定宽度为 N 且无填充位的无符号整数类型。因此,uint24_t表示这样一种无符号整数类型,其宽度正好为 24 位。这些类型是可选的。但是,如果实现提供了宽度为 8、16、32 或 64 位的整数类型,没有填充位,并且(对于有符号类型)具有二进制补码表示,则它应定义相应的 typedef 名称。
类型 uint64_t 正好有 64 个值位并且没有填充位,因此不能有任何陷阱表示。
【问题讨论】:
-
我不是 C 标准方面的专家,但两者之间有一个重要的区别。
union版本确保u的起始地址满足两个字段的对齐限制。memcpy版本不保证bits位于double边界上。如果double对齐比uint64_t更严格,它可能会失败,例如当printf尝试计算bits的double字符串表示时出现总线错误。 -
@Gene:我没有看到任何对齐问题:
memcpy在非对齐块之间复制是明确安全的,传递给printf的值是从它们的有效类型中读取的。 -
@RichardChambers:但是,当通过不同类型的左值访问时,该值被检索为左值的类型,而不是原始类型。 这正是定义类型双关语和上述代码的目的。您的回答也是如此:是的,
memcpy可用于类型双关语。 -
第 6 段中关于
memcpy()的部分不适用于这种情况,因为uint64_t bits具有声明的类型。 -
@EOF:好点。我编辑了问题。
标签: c type-conversion language-lawyer