类型的表示(一)
一、概述
除位字段(bit-fields)外,对象由一个或者多个连续字节组成;字节的数量、顺序和编码要么明确指定,要么由实现定义。
int number = -2; size_t size = sizeof(int); unsigned char arr[size]; memcpy(arr, &number, sizeof(int)); for(size_t i=0; i<size; i++) printf("%#X ", arr[size-1-i]); //输出0XFF 0XFF 0XFF 0XFE。
number的32位表示形式为FFFFFFFE(十六进制)。
无符号位字段和unsigned char类型对象的值应使用纯二进制表示法(pure binary notation)表示。
纯二进制表示法是一种使用二进制数0和1的整数位置表示法,其中连续位表示的值是加法,从1开始,并乘上连续的2的整数幂,但最高位可能除外。一个字节包含CHAR_BIT个位,unsigned char类型的值范围从0到2CHAR_BIT-1。unsigned char类型的对象表示中每个位都参与值表示,没有填充位(padding bits),因此不存在非值表示(non-value representation)。
任何其它类型的非位字段对象值由n×CHAR_BIT个位组成,其中n是对象大小(单位:字节)。该值可以使用memcpy函数复制到unsigned char [n]类型的对象中,结果字节集称为该值的对象表示(the object representation of the value)。如果位字段的值由m个位组成,其中m是位字段的大小;位字段的对象表示是位字段在可寻址存储单元中的m个位的集合。具有相同对象表示的两个值(NaNs除外)相等;但相等的值可能具有不同的对象表示。
int number = 1; float data = 1.0f; size_t sizeInt = sizeof(int); size_t sizeFloat = sizeof(float); unsigned char arrInt[sizeInt]; unsigned char arrFloat[sizeFloat]; memcpy(arrInt, &number, sizeInt); memcpy(arrFloat, &data, sizeFloat); for(size_t i=0; i<sizeInt; i++) printf("%#X ", arrInt[sizeInt-1-i]); //输出0 0 0 0X1。 puts(""); for(size_t i=0; i<sizeFloat; i++) printf("%#X ", arrFloat[sizeFloat-1-i]); //输出0X3F 0X80 0 0。
int类型的number和float类型的data值是相等的;但对象表示却是不同的,分别为00000001、3F800000(十六进制)。
某些对象表示不表示对象类型的值;如果对象值具有这样的表示形式,由非字符类型的左值表达式读取时,其行为是未定义的。如果这种对象表示是由副作用生成的,该副作用通过非字符类型的左值表达式修改对象的全部或者部分,其行为是未定义的。这种对象表示称为非值表示(non-value representation)(注:ISO/IEC 9899:2024标准前这种对象表示称为陷阱表示(trap representation))。
bool a; const char b = 'b'; memcpy(&a, &b, sizeof(bool)); printf("%#X\n", a); //a的表示形式是非值表示,这里将输出0X62。
自动变量可以初始化为非值表示,且不会导致未定义行为;但在存储正确值之前,不能使用变量值。
结构和联合(包括成员)中填充字节的值都是未指定的;结构和联合赋值时可能无法复制任何填充位。结构和联合的对象表示永远不会是非值表示,即使其成员对应的字节范围可能是该成员自身的非值表示。
struct s{ bool b; int i; }; ... struct s ss1, ss2; //ss1所有位为1。 //ss1.b对象表示是非值表示。 memset(&ss1, 0xFF, sizeof(ss1)); //可以复制,对象表示有效。 //ss2.b对象表示是非值表示。 memcpy(&ss2, &ss1, sizeof(ss2));
对于联合成员,对象表示中与当前成员不对应,但与其它成员对应的字节值是未指定的。
union u{ unsigned char ch; int i; }; ... union u data = {.i=321}; data.ch = 'a'; size_t size = sizeof(union u); unsigned char arr[size]; memcpy(arr, &data, size); for(size_t i=0; i<size; i++) printf("%#X ", arr[i]); //输出0X61 0X1 0 0。
联合对象data占4个字节,对象表示为61010000(十六进制),成员data.ch占1个字节,其它3个字节都不是和成员data.ch对应的,因此其值是未指定的。
具有多种对象表示的值用作运算符的操作数时,无论使用哪种对象表示都不应该影响结果值。对于某种类型,如果一个值具有多种对象表示,当该值存储到该类型对象时,ISO/IEC 9899:2024标准未指定使用哪种对象表示;但无论使用哪种对象表示,都不应该生成非值表示。
具有相同有效类型T的对象x和y,作为T类型的对象访问时可能具有相同值;但在其它上下文中可能具有不同值。
unsigned char ch = 100; unsigned int i = 356; /*作为unsigned char类型。*/ printf("%c\n", ch); //输出d。 printf("%c\n", i); //输出d。 /*作为十进制unsigned int类型。*/ printf("%u\n", ch); //输出100。 printf("%u\n", i); //输出356。
对于某类型T,如果定义了==,即使x==y,也不意味memcmp(&x,&y,sizeof(T)) == 0;x==y并不一定意味x和y具有相同值;对于类型T的值,其它操作可以区分x和y。
float f1 = +0.0f; float f2 = -0.0f; if(f1 == f2) puts("The two values are equal."); else puts("The two values are not equal."); if(memcmp(&f1, &f2, sizeof(float)) == 0) puts("The two object representations are the same."); else puts("The two object representations are different.");
将输出:
The two values are equal.
The two object representations are different.
原子类型对象的加载和存储采用顺序一致性(memory_order_seq_cst)语义。