[C语言]struct和union的区别 大小端 字节对齐详解

一、struct和union的区别

  1. struct:
struct  A{
           int a;

           long *b;

           char c[20];

};

可以看到struct是由各种基本数据类型组合成的一个结构,我们也可以理解为另外一个数据类型,只不过这个数据类型不是C语言语法里面规定的像char、int这样的基本单位。既然是数据类型就具有内存,下面看一段代码:

#include <stdio.h>

struct A {

};

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

struct C {
	char a;
	int b;
	int c;
};


void test_struct1() 
{
	struct A m_a;
	struct B m_b;
	struct C m_c;
	
	printf("sizeof(struct A) = %d\n", sizeof(m_a));
	printf("sizeof(struct B) = %d\n", sizeof(m_b));
	printf("sizeof(struct C) = %d\n", sizeof(m_c));
}
int main()
{
	test_struct1();
	return 0;
}

运行输出结果:
在这里插入图片描述
可以看到一个空的结构体的内存大小为0,struct B的数据类型的内存大小为int+int+int等于12,是所有成员变量的和,但是为什么struct C的内存大小不是char+int+int等于9呢?因为有字节对齐,在第二部分会讲到字节对齐就明白了。

结构体的嵌套使用
结构体的自引用,就是在结构体内部,包含指向自身类型结构体的指针。
结构体的相互引用,就是说在多个结构体中,都包含指向其他结构体的指针。
(1)自引用结构体
不使用typedef时
错误的方式:

struct D {
	int a;
	struct D d;
};

看看编译结果:
在这里插入图片描述
编译器直接报错了,这种声明是错误的,因为这种声明实际上是一个无限循环,成员D是一个结构体,D的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。
正确用法是使用指针:

struct D {
	int a;
	struct D* d;
};

此时编译通过:
在这里插入图片描述
由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。

如果使用typedef该如何定义呢?
先看一下一个简单的例子:

typedef struct E {
	int a;
}T_E;

这个编译器是可以编译通过的,我们再看看:

typedef struct E {
	int a;
	T_E* e;
}T_E;

编译的结果:
在这里插入图片描述
编译器直接提示未定义T_E,这是因为使用typedef为结构体创建一个别名T_E。但是这里是错误的,因为类型名的作用域是从语句的结尾开始,而在结构体内部是不能使用的,因为还没定义。
正确的方式:

typedef struct _t_e{
	int a;
	struct _t_e* e;
}T_E;

此时编译通过:
在这里插入图片描述
(2)相互引用 结构体
错误的方式:

typedef struct _t_f{
    int a;
    G *gp; 
} F;

typedef struct _t_g{
    int a;
    F *ep;
} G;

编译报错:
在这里插入图片描述

错误的原因和上面一样,这里类型B在定义之前 就被使用。
正确的方式:

typedef struct _t_e{
	int a;
	struct _t_e* e;
}T_E;


struct _t_g{
    struct _t_f *fp; 
    int a;
};
struct _t_f{
    struct _t_g *gp;
    int a;
};
typedef struct _t_g G;
typedef struct _t_f F;
  1. union
typedef union
{
	char A;
	int B;
	short C;
}UN;

可以看到union也是由各种基本数据类型组合成的一个结构,但是它和结构体有什么区别呢?
结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
先来看一段代码:

union AA {

};

union BB {
	int a;
	int b;
	int c;
};

union CC {
	char a;
	int b;
	int c;
};

printf("sizeof(struct AA) = %d\n", sizeof(m_aa));
printf("sizeof(struct BB) = %d\n", sizeof(m_bb));
printf("sizeof(struct CC) = %d\n", sizeof(m_cc))

我们看一下 AA BB CC 分别占用多少内存:
在这里插入图片描述
可以看到没有成员的union和struct一样不占用内存,拥有成员int int int和拥有char int int的都占用4个字节的内存,也就是说union所有成员占用同一段内存,以最大的成员所占有的内存所分配内存。我们再来看看一段代码:

	union AA m_aa;
	union BB m_bb;
	union CC m_cc;
	m_bb.a = 10;
	printf("m_bb.a = %d, m_bb.b = %d, m_bb.c = %d\n", m_bb.a, m_bb.b, m_bb.c);

运行结果:
在这里插入图片描述
可以看到我们在对union的成员a赋值的时候,b,c也拥有同样的值,也就是说明a,b,c拥有共同的内存。
我们再看一段代码:

	union CC m_cc;
	m_cc.b = 20;
	printf("m_cc.a = %d, m_cc.b = %d, m_cc.c = %d\n", m_cc.a, m_cc.b, m_cc.c);

运行结果:
在这里插入图片描述
可以看到union CC中的所有成员也被赋值成同一个值。

二、字节对齐
字节对齐有3条规则:
规则1:结构体的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员放在offset为该数据成员大小的整数倍的地方。如:int在32位机为4字节,则要从4的整数倍地址开始存储。

规则2:如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为A内部最大成员的整数倍的地方开始存储。(struct B里存有struct A,A里有char、int、double等成员,那A应该从8的整数倍开始存储。)结构体A中的成员的对齐规则仍满足原则1、原则2。

规则3:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足要补齐。

我们来看一个例子:

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

先来看看struct C占用的内存大小:
在这里插入图片描述
12个字节,这个就和字节对齐有关系:(在32位系统环境,编译选项为4字节对齐)
struct中a占1个字节,b占4个字节,c占2个字节,但因为了满足(结构体每个成员相对于结构体首地址的偏移量offset都是成员大小的整数倍),a需要占4个字节,这样b的offset就是4了,同时为了满足(结构体总大小是内部最大成员整数倍),c需要4个字节。所以一共12个字节。我们来看各自的内存占用情况:
在这里插入图片描述

三、用union区分大小端
到这了我们来想一个问题:如何用union区分大小端,首先我们先来了解一下什么是大小端:
比如一个short类型的变量:对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。

假如现有一32位int型数0x12345678,那么其MSB(Most Significant Byte,最高有效字节)为0x12,其LSB (Least Significant Byte,最低有效字节)为0x78,在CPU内存中有两种存放方式:(假设从地址0x4000开始存放)
在这里插入图片描述
了解完大小端,我们来用union判断一下我们的服务器是大端还是小端的:
来看一段代码:

union CC {
	char a;
	int b;
	int c;
};

union CC m_cc;
m_cc.b = 10;
if(m_cc.a)
	printf("little-endian\n");
else
	printf("big-endian\n");

这里定义了一个union的CC,对成员int b进行赋值为10,因为union是公用内存的,如果是小端模式,10应该存放在地址起始的位置上,那么a成员的值也是10,否则a应该为0,以此来分辨大小端。
在这里插入图片描述
四、代码分析:

#include <stdio.h>

struct A {

};

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

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

struct D {
	int a;
	struct D* d;
};

typedef struct _t_e{
	int a;
	struct _t_e* e;
}T_E;


struct _t_g{
    struct _t_f *fp; 
    int a;
};
struct _t_f{
    struct _t_g *gp;
    int a;
};
typedef struct _t_g G;
typedef struct _t_f F;


union AA {

};

union BB {
	int a;
	int b;
	int c;
};

union CC {
	char a;
	int b;
	int c;
};

void test_struct1() 
{
	struct A m_a;
	struct B m_b;
	struct C m_c;
	
	union AA m_aa;
	union BB m_bb;
	union CC m_cc;
	
	printf("sizeof(struct A) = %d\n", sizeof(m_a));
	printf("sizeof(struct B) = %d\n", sizeof(m_b));
	printf("sizeof(struct C) = %d\n", sizeof(m_c));
	
	printf("sizeof(struct AA) = %d\n", sizeof(m_aa));
	printf("sizeof(struct BB) = %d\n", sizeof(m_bb));
	printf("sizeof(struct CC) = %d\n", sizeof(m_cc));
	
	m_bb.a = 10;
	printf("m_bb.a = %d, m_bb.b = %d, m_bb.c = %d\n", m_bb.a, m_bb.b, m_bb.c);
	
	m_cc.b = 20;
	if(m_cc.a)
		printf("little-endian\n");
	else
		printf("big-endian\n");
	printf("m_cc.a = %d, m_cc.b = %d, m_cc.c = %d\n", m_cc.a, m_cc.b, m_cc.c);
}
int main()
{
	test_struct1();
	return 0;
}

makefile:

CC = gcc
CFLAGS = -g -Wall -O
main:test.o
	$(CC) $^ -o $@
%.o:%.c
	$(CC) $(CFLAGS) -c $^
clean:
	rm -rf test.o
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值