CSPP学习笔记-Ch3.9 异构数据结构

3.9 异构数据结构(Heterogeneous Data Structures)

3.9.1 结构(struct)

C 语言的 struct 声明创建一个数据类型,将可能不同类型的对象聚合到一个对象中
结构的所有组成部分都存放在内存中一段连续的区域(不同字段之间由于字节对齐的要求,可能存在一定长度的“间隙”、和末尾的“填充”——不被使用的内存字节);
指向结构的指针就是结构第一个字节的地址。

🌰

struct rec{ int i; int j; int a[2]; int *p; };
// 这个结构包括4个字段,一共24个字节长度。

为了访问结构的不同字段,编译器产生的代码要讲结构的地址加上适当的偏移。

# 假设struct rec*类型的变量r放在寄存器%rdi中

## 将i复制到j
	# Registers: r in %rdi
	# 字段i的偏移量是0,访问j加上一个偏移量4
	movl (%rdi), %eax 		# Get r->i
	movl %eax, 4(%rdi) 		# Store in r->j
	
## 获得&(r->a[i])的值
	# Registers: r in %rdi, i %rsi
	leaq 8(%rdi,%rsi,4), %rax 		# Set %rax to &r->a[i]
	
## 实现代码:r->p = &r->a[r->i + r->j];
	# Registers: r in %rdi
	movl 4(%rdi), %eax 			# Get r->j
	addl (%rdi), %eax 			# Add r->i
	cltq 						# Extend to 8 bytes
	leaq 8(%rdi,%rax,4), %rax 	# Compute &r->a[r->i + r->j]
	movq %rax, 16(%rdi) 		# Store in r->p

【总结】结构的各个字段的选取完全是在编译时处理的。(机器代码不包含关于字段声明、名字的信息)

3.9.2 联合(union)

允许以多种类型来引用一个对象——联合的大小是它最大字段的大小
指向联合的指针的表达式由多种,🌰设指针 p 是指向 union U3 的指针,那么表达式 pp->cp->i[0]p->v 引用的都是联合 U3 的起始位置。

union U3{ char c; 	int i[2]; 	double v; };

【应用一】联合的优点在于可以节省一部分的内存空间👇(尤其是字段很多的时候,节省空间的效果越明显)
🌰:实现一个二叉树的结点,每个结点都有两个 double 类型的数据值,还有两个孩子结点的指针。

/* 👇每个结点需要32个字节
	当结点表示数据时,两个指针占据的内存就是一种浪费;
    当结点表示指针时,两个double占据的内存也是一种浪费。*/
struct node_s {
	struct node_s *left;
    struct node_s *right;
    double data[2];
};
/* 改进,用联合:👇每个结点只需要16个字节
	如果n是一个指针,指向union node_u*类型的结点,
	可以用n->data[0]和n->data[1]来引用叶子结点的数据,
	而用n->internal.left和n->internal.right来引用内部结点的孩子。*/
union node_u {
    struct {
        union node_u *left;
        union node_u *right;
    } internal;
    double data[2];
};// 缺点:没有办法确定一个给定的结点到底是叶子结点还是内部数据。
/* 进一步改进,引入枚举:👇每个结点需要16+4+(+4)=24个字节(+4是字节对齐需要) */
typedef enum { N_LEAF, N_INTERNAL } nodetype_t;	// 标签字段,类型int
struct node_t {
    nodetype_t type;
    union {
        struct {
            struct node_t *left;
            struct node_t *right;
        } internal;
    	double data[2];
    } info;
};

【应用二】可以用来访问不同数据类型的位模式
🌰使用简单的强制类型转换:将一个 double 类型的值 d 转换为 unsigned long 类型的值 u

double d;
unsigned long u = (unsigned long) d;

结果:
值 u 是 d 的整数表示,且 u 和 d 具有一样的位级表示,包括符号位字段、指数和尾数。
(d 的值等于 0.0 时除外)

【应用三】判断字节顺序
可以用 union 来判断字节顺序,是大端的还是小端的。

double uu2double(unsigned word0, unsigned word1){
    union{ 
        double d;
        unsigned u[2]; 
	} temp;
    temp.u[0] = word0;
    temp.u[1] = word1;
    return temp.d;
}

在小端法机器上,参数 word0d 的低位 4 个字节,参数 word1d 的高位 4 个字节。大端法机器上则相反。

3.9.3 数据对齐

为啥需要数据对齐?
(1)可移植性:某些型号的 Intel 和 AMD 处理器对于有些实现多媒体操作的 SSE 指令,如果数据没对齐,就无法正确执行——导致异常。
(2)提升性能:CPU 每次读取内存时是以“块”为单位的,如果数据不对齐,目标对象可能就跨越了两个内存块,CPU 要把两个块都读进来,再去掉无关字节,再拼接起来,这降低了 CPU 的读取数据的效率。

对齐原则:任何 K 个字节的基本对象的地址必须是 K 的倍数。

K类型
1char
2short
4int,float
8long,double,char*
16SSE 指令,大多数函数的栈帧边界,
alloca、malloc、calloc、realloc 生成的块的起始地址

编译器在汇编代码中放入命令来指明全局数据所需的对齐:.align 8,这保证了其后面的数据的起始地址是 8 的倍数。

数据对齐,会在字段之间产生了一些“间隙”、“末尾填充”,这些是未被使用的空间(也估计是不会被使用的空间)。

🌰计算结构体的字节总量:

struct{
	char *a;	short b;	double c;	char c;		float e;	char f;		long g;		int h;
} rec;

字节总量 = [ 8 ] + [ 2 + 6 ] + [ 8 ] + [ 1 + 4 + 1 + ( 2 ) ] + [ 8 ] + [ 4 + ( 4 ) ] = 56 =[8]+[2+6]+[8]+[1+4+1+(2)]+[8]+[4+(4)]=56 =[8]+[2+6]+[8]+[1+4+1+(2)]+[8]+[4+(4)]=56​​​​,其中圆括号里的是数据对齐产生的“间隙”和“末尾填充”。
重新排列,最小化使用空间总量:
可能的排列之一:

struct{
	char *a;	double c;	long g;		int h;		float e;	short b;	char c;		char f;
} rec;

最小的字节总量 = [ 8 ] + [ 8 ] + [ 8 ] + [ 4 + 4 ] + [ 2 + 1 + 1 + ( 4 ) ] = 40 =[8]+[8]+[8]+[4+4]+[2+1+1+(4)]=40 =[8]+[8]+[8]+[4+4]+[2+1+1+(4)]=40​​。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值