这是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; ud = 1234.5678; printf("the representation of %g is %08"PRIX64"n", d, ui);
如果我使用我的版本的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 name
int
N_t
指定一个有符号整数类型,其宽度为N ,无填充位和二进制补码表示。 因此,int8_t
表示这样的带符号整数类型,其宽度恰好为8位。2 typedef名称
uint
N_t
指定一个宽度为N且无填充位的无符号整数类型。 因此,uint24_t
表示这种无符号整数类型,其宽度恰好为24位。这些类型是可选的。 但是,如果实现提供宽度为8,16,32或64位的整数类型,没有填充位,并且(对于具有二进制补码表示的有符号类型),它应定义相应的typedef名称。
类型uint64_t
恰好有64个值位且没有填充位,因此不能有任何陷阱表示。
有两种情况需要考虑: memcpy()
进入一个具有声明类型的对象, memcpy()
进入一个没有声明类型的对象。
在第二种情况下,
double d = 1234.5678; void *p = malloc(sizeof(double)); assert(p); uint64_t *pbits = memcpy(p, &d, sizeof(double)); uint64_t bits = *pi; printf("the representation of %g is %08"PRIX64"n", d, bits);
行为确实是未定义的,因为p
指向的对象的有效类型将变为double
,并且通过uint64_t
类型的左值访问有效类型为double
的对象是未定义的。
另一方面,
double d = 1234.5678; uint64_t bits; memcpy(&bits, &d, sizeof bits); printf("the representation of %g is %08"PRIX64"n", d, bits);
没有定义。 C11标准草案n1570:
7.24.1字符串函数约定
3
对于本子条款中的所有函数,每个字符都应解释为它具有unsigned char类型(因此每个可能的对象表示都是有效的并且具有不同的值)。
和
6.5表达式
7
对象的存储值只能由具有以下类型之一的左值表达式访问:88) – 与对象的有效类型兼容的类型,
– 与对象的有效类型兼容的类型的限定版本,
– 对应于对象的有效类型的有符号或无符号类型,
– 对应于对象有效类型的限定版本的有符号或无符号类型,
– 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或者
– 一个字符类型。
所以memcpy()
本身是明确定义的。
正弦uint64_t bits
具有声明的类型 ,即使其对象表示从double
精度复制,它仍保留其类型。
正如chqrlie指出的那样, uint64_t
不能有陷阱表示,因此在memcpy()
之后访问bits
不是未定义的,只要sizeof(uint64_t) == sizeof(double)
。 但是, bits
的值将取决于实现(例如,由于字节序)。
结论 :只要memcpy()
的目标确实具有声明的类型,即不是由[m/c/re]alloc()
或等效的分配, memcpy()
可以用于类型惩罚。
你提出了3种方法,它们都与C标准有不同的问题。
根据标准明确定义的唯一方法是将double
的表示存储在正确大小的char数组中,然后显示char数组的字节值:
double d = 1234.5678; unsigned char bits[sizeof(d)]; memcpy(&bits, &d, sizeof(bits)); printf("the representation of %g is ", d); for(int i=0; i
并且只有实现对char
使用恰好8位时,结果才可用。 但它是可见的,因为如果其中一个字节的值大于255,它将显示超过8个六位数。
以上所有内容仅有效,因为bits
具有声明的类型。 请参阅@ EOF的答案 ,了解为什么分配的对象会有所不同
我读了第6段的话说,使用memcpy()
函数将一系列字节从一个内存位置复制到另一个内存位置可以用于类型惩罚,就像使用具有两种不同类型的union
可以用于类型惩罚一样。
第一次提到使用memcpy()
表示如果它复制指定的字节数,并且当该变量(左值)用于存储字节时,这些字节将与源目标的变量具有相同的类型。
换句话说,如果你有一个变量double d;
然后,您为此变量(左值)分配一个值,该变量中存储的数据类型为double
类型。 然后,如果您使用memcpy()
函数将这些字节复制到另一个内存位置,比如变量uint64_t bits;
这些复制字节的类型仍然是double
。
然后,如果您通过目标变量(左值)访问复制的字节,则uint64_t bits;
在该示例中,该数据的类型被视为用于从该目标变量检索数据字节的左值的类型。 因此,字节被解释(不转换但解释)为目标变量类型,而不是源变量的类型。
通过不同类型访问字节意味着字节现在被解释为新类型, 即使字节实际上没有以任何方式改变 。
这也是union
工作方式。 union
不做任何转换。 您将字节存储到一个类型的union
成员中,然后通过不同的union
成员将相同的字节拉回。 字节是相同的,但字节的解释取决于用于访问存储区的union
成员的类型。
我已经看到旧的C源代码中使用的memcpy()
函数通过使用struct
member offset和memcpy()
函数将struct
变量的一部分复制到其他struct
变量中来帮助将struct
划分为多个部分。
因为memcpy()
使用的源位置的类型是存储在那里的字节的类型,使用union
进行惩罚可以遇到的同类问题也适用于以这种方式使用memcpy()
作为数据类型的Endianness 。
要记住的是,无论是使用union
还是使用memcpy()
方法,复制的字节类型都是源变量的类型,然后当您再次访问数据时,无论是通过union
的不同成员还是通过memcpy()
的目标变量,字节被解释为目标左值的类型。 但是实际的字节不会改变。
改变 – 见下文
虽然我从未观察到编译器将非重叠源和目标的memcpy解释为执行任何不等同于将源的所有字节读取为字符类型然后将目标的所有字节写为一个字符类型(意思是如果目标没有声明的类型,它将没有有效的类型),标准的语言将允许钝的编译器进行“优化” – 在极少数情况下,编译器将是能够识别和利用它们 – 更有可能破坏原本可行的代码(如果标准写得更好,将会很好地定义),而不是实际提高效率。
至于这是否意味着最好使用memcpy或手动字节复制循环,其目的被充分伪装成无法识别为“复制字符类型数组”,我不知道。 我认为明智的做法是避免任何人如此迟钝,以至于建议一个好的编译器应该在没有这种混淆的情况下产生虚假代码,但是由于过去几年被认为是钝的行为目前很流行,我不知道是否memcpy
将成为破坏代码竞争的下一个受害者,编译器几十年来将其视为“明确定义”。
UPDATE
从6.2开始,GCC有时会忽略memmove操作,即使它们是不同类型的指针,它们也会看到目标和源识别相同的地址。 如果稍后将作为源类型写入的存储读取为目标类型,则gcc将假定后一个读取不能识别与先前写入相同的存储。 gcc的这种行为是合理的,因为标准中的语言允许编译器通过memmove
复制有效类型。 目前还不清楚这是否是对memcpy
规则的故意解释,但是,鉴于gcc在标准明显不允许的某些情况下也会进行类似的优化,例如当一个类型的工会成员时(例如64- bit long
)被复制到临时的并从那里复制到具有相同表示的不同类型的成员(例如,64位long long
)。 如果gcc发现目标将与临时位置逐位相同,则会省略写入,因此无法注意到存储的有效类型已更改。
它可能会给出相同的结果,但编译器不需要保证它。 所以你根本不能依赖它。
以上就是c/c++开发分享memcpy可以用于打字吗?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/560590.html