结构体和大小端字节序

自定义类型包括:结构体,枚举,联合体

1. 结构体
2. 结构体内存对齐
3. 大端字节序
4. 小端字节序
5. 如何拿程序测试机器是大端还是小端
6. 为什么会有小端字节序

1.结构体是一些值的集合,这些值被称为成员变量
结构的每个成员可以是不同类型的变量。但反过来说结构体本身就是一个独立的变量,它是一个自定制类型。

结构体声明
struct tag{member -list;}variable -list;
结构体变量的定义和初始化

struct students
{
    char name[10];
    int age;
    char sex[10];
} bls = {"marry", 21, "woman"};//申明时创建结构体变量bls并初始化

结构体内引用自身需要用到结构体指针

struct node {
	int data;
	struct node *next;
};
int main(void){
	struct node *head, first, second;
	head = &first;
	first.data = 1;
	first.next = &second;
	second.data = 2;
	second.next = NULL;
	while (head) {
		printf("%d\n", head->data);
		head = head->next;
	}
	return 0;
}

结构体内存对齐

  • 1.结构体内存对齐第一个成员在与结构体变量偏移量为0的地址处
  • 2.其他成员变量要对齐到某个数字(对其数)的整数倍的地址处
    “对齐数” =【编译器默认的一个对齐数与该成员大小的较小值】
  • 3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数,就是成员变量自身大小与编译器默认的对齐数的最小值) 的整数倍.为了加快数据存取的速度,编译器默认情况下会对结构体成员和结构体本身存储位置进行处理,使其存放的起始地址是一定字节数的倍数,而不是顺序存放,这称之为字节对齐(内存对齐或自然对齐).

分析例子B;

struct B {
	char b;
	int a;
	short c;
};

假设B从地址空间0x0000开始排放。该例子中没有定义默认的对齐数,在本人环境下,编译器默认的对齐数为4
第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐数4小,所以其有效对齐值为1,所以其存放地址 0x0000符合0x0000%1=0
第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,符合0x0004%4=0,且紧靠第一个变量。
第三个变量c,自身对齐值为2 ,所以有效对齐值也是2 ,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0
所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4,10%4 != 0
所以根据结构体圆整的要求, 0x0009到0x0000=10字节(10+2)%4=0。所以故B共有12个字节,sizeof(struct B)=12;

其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4, 取一个变量的自身对齐值(自身大小)与编译器给予默认对齐值较小的值为对齐数。

内存对齐在cpu层面的意义
https://blog.csdn.net/qq635075803/article/details/80300453

现在我们做个假设,假设只支持4字节的传输(单纯的假设,也就是cpu只能隔着4字节寻址),现在内存中有8字节的内存依次存放着三个单字节字符型、一个四字节整型和一字节空数据
(char char char int32 0),那么你在用高级语言请求访问字符或者整型的时候CPU的总线操作就会相当复杂甚至出错,因为处理器不支持,但是如果事先用16个字节的空间进行内存对齐(char000 char000 char000 int32)那么就可以取出想要的数据,程序就可以正常运行。

总而言之,内存对齐规则在cpu和内存的层面上降低了时间的开销,但相应的增加了内存的空间,但 对于现在的计算机来说,存储是成本降低,我们主要考虑时间上的开销

在这里列出两种结构成员变量的声明方法

struct agling{
	char a;
	int b;
	char c;    
 }
struct agling2{
	int b;
	char a;
	char c;
}

agling2 所包含的成员和前面那个结构一样,但它只占八个字节,节省了百分之三十三 两个字符可以紧挨着存储。
在这里插入图片描述

在这里插入图片描述

修改默认对齐数

#pragma pack(8) //修改默认编译器对齐数为8

百度笔试题写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明

写出一个宏,求任意结构体成员相对于结构体首地址的偏移。

#define  STRUCT_OFFSET(id, element)  ((unsigned long)(&(( struct id*)0)->element))

因为是求偏移量 所以假设结构体的首地址是0开始,把它转换成结构体指针类型,在用“->”取得它的成员,前面加了一个& 就是取得这个成员的地址,最后在强制转换成unsigned long,这样就得到了偏移量。
我的理解,关键操操作是吧0强转成结构体指针,也就是从0地址定义了个结构体,那么偏移地址也就是成员变量的地址

位段

位段的内存分配
位段成员 int , unsigned int , signed int, 或者是char
位段设计很多不确定因素,位段不跨平台原因在下
1.int位段是有符号数,还是无符号数

2.位段中最大的数目是不确定的,(16位机器最大16,32位机器最大32)

3.位段中的成员在内存中,从左到右分配,还是从右到左分配?


struct Student
{
	int aa : 3;
	int bb : 6;
	int cc : 10;
	int dd : 30;
};

位段中的最大数目.许多编译器把位段成员的长度限制在一个整形值的长度之内,所以一个能够运行于32位整数的机器上的位段可能在16位整数机器上无法运行位段中成员在内存中是从左向右分配的还是从右 向左分配的.
当一个声明指定了两个位段,第二个位段比较大,无法容纳第一个位段剩余的位时,编译器有可能把第二个位段放在内存的下一个字,也可能直接放在第一个位段后边,从而在两个内存位置的边界上形成重叠
在位段中 存在着一种分配方使得大端字节序机器和小端字节序机器两种情况所对应的成员变量的值所不同。

既然提到大小端字节序,我们不妨研究一下大小端字节序的规则及其规则设定的原因大端字节序:低地址存放高位,高地址存放低位(和人正常的写作习惯相同)
小端字节序:高地址存放高位,低地址存放低位;(一般的机器内存方式)

1. 大端字节序

1个字节=2个16进制字符,一个16进制位=0.5个字节
我们拿16进制数字0x12345678来举例,0x12345678使用三个字节储存:字节位序由高到低是12 34 56 78

大端字节序的存储即0x12345678
大端字节序:高位字节在前(低地址),低位字节在后(高地址),(低地址存高位,高地址存低位)这是人类读写数值的方法。
如图所示:
大端字节序
在这里插入图片描述

2. 小端字节序

我们拿16进制数字0x12345678来举例,0x12345678使用三个字节储存:字节位序由高到低是12 34 56 78

小端字节序的存储即0x78563412
小端字节序:低位字节在前,高位字节在后。这是小端编译器读取数值的方法。
如图所示:
小端字节序
在这里插入图片描述

这里简单的概述下机器使用小段字节序的原因,因为计算机的计算通常是由低位指向高位的,也就是说一旦数据参与运算,低位先进行运算 再对高位进行运算,这时小端字节序就得到应用。但是,人类还是习惯读写大端字节序。所以,除了计算器的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件存储。

回归正题,机器的大小端对位段的成员变量的影响因为位段成员变量的内存分配在计算机中从低地址到高地址,无论是小段字节序还是大段字节序,都是如此规则。我们举个例子

#include <stdio.h>
int main()
{
    typedef unsigned int uint16_t;
    struct myst
    {
        uint16_t a : 3;
        uint16_t b : 8;
        uint16_t c : 5;
    };
    struct myst *p;
    int t = 0x2041;
    p = ((struct myst *)&t);
    printf("%d", p->c);
    return 0;
}
 

0x2041在大端中内存为 0010 0000 0100 00010×2041
在小端内存中为1000 0010 0000 0100

所以在小端中a为001 b为00001000 c为00100(按照电脑的思想从低地址向高地址走思考 )
所以在大端中a为001 b为00000010 c为00001(按照人的思想走)

5. 如何拿程序测试机器是大端还是小端

#include <stdio.h>

union check
{
    int i;
    char ch;
} c;

int main()
{
    c.i = 1;
    c.ch == 1 ? printf("小端\n") : printf("大端\n");
    return 0;
}

运行得到结果是小端,低地址存低位
在这里插入图片描述

6. 为什么会有小端字节序

计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。在计算机内部,小端序被广泛应用于现代 CPU 内部存储数据;而在其他场景,比如网络传输和文件存储则使用大端序。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值