当前位置: C语言 -- 基础 -- 类型的表示

类型的表示(一)

一、概述

除位字段(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。

number32位表示形式为FFFFFFFE(十六进制)。


无符号位字段和unsigned char类型对象的值应使用纯二进制表示法(pure binary notation)表示。

纯二进制表示法是一种使用二进制数01的整数位置表示法,其中连续位表示的值是加法,从1开始,并乘上连续的2的整数幂,但最高位可能除外。一个字节包含CHAR_BIT个位,unsigned char类型的值范围从02CHAR_BIT-1unsigned char类型的对象表示中每个位都参与值表示,没有填充位(padding bits),因此不存在非值表示(non-value representation)。


任何其它类型的非位字段对象值由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类型的numberfloat类型的data值是相等的;但对象表示却是不同的,分别为000000013F800000(十六进制)。


某些对象表示不表示对象类型的值;如果对象值具有这样的表示形式,由非字符类型的左值表达式读取时,其行为是未定义的。如果这种对象表示是由副作用生成的,该副作用通过非字符类型的左值表达式修改对象的全部或者部分,其行为是未定义的。这种对象表示称为非值表示(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。

联合对象data4个字节,对象表示为61010000(十六进制),成员data.ch1个字节,其它3个字节都不是和成员data.ch对应的,因此其值是未指定的。


具有多种对象表示的值用作运算符的操作数时,无论使用哪种对象表示都不应该影响结果值。对于某种类型,如果一个值具有多种对象表示,当该值存储到该类型对象时,ISO/IEC 9899:2024标准未指定使用哪种对象表示;但无论使用哪种对象表示,都不应该生成非值表示。


具有相同有效类型T的对象xy,作为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)) == 0x==y并不一定意味xy具有相同值;对于类型T的值,其它操作可以区分xy

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)语义。