结构体详解应用及内存对齐

1.结构体的概念及初始化

结构体就是许多值的一个集合,这些值就称作成员变量,结构的成员可以是很多类型的变量。

那初始化结构体的方法如下

声名一个结构体

struct book
{

	char name[20];
	char author[20];
	float price;
	char id[13];

};

这个结构体的类型就是struct

给结构体赋值

int main()
{

	struct book b1 = { "git","gay",17.6,"aidh" };
	struct book b2 = { .id = "aidha",.author = "peng",.name = "abyhiak", .price = 12.3 };
						
}

给结构体赋值时(1)按默认顺序一个一个赋值。

                        (2)用.加变量名赋值  

在访问结构体时有两种方式

1.用结构体名.跟变量名字 来访问

struct book
{

	char name[20];
	char author[20];
	float price;
	char id[13];

};


int main()
{

	struct book b1 = { "git","gay",17.6,"aidh" };
	struct book b2 = { .id = "aidha",.author = "peng",.name = "abyhiak", .price = 12.3 };
	printf("%s", b1.author);
}

  2. 把结构体给一个指针存储  指针名+->变量名 这样也可以赋值 访问

struct book
{

	char name[20];
	char author[20];
	float price;
	char id[13];

};


int main()
{

	struct book b1 = { "git","gay",17.6,"aidh" };
	struct book b2 = { .id = "aidha",.author = "peng",.name = "abyhiak", .price = 12.3 };
	struct book* s1 = &b1;
	printf("%s", s1->author);
}

2.结构体的一些应用

1.特殊类型的结构体

匿名结构体类型

struct   //无标签名   匿名结构体只能用一次
{
	char name[20];

} s = {"abc"};

如果没有重命名的情况下只能使用一次。

2.结构体自引用

结构体内部还可以包含结构体

所以就可以实现一个链表的形成

struct Node
{
	int data;
	struct Node* next;
};

这里为了简化这个链表我们给他一个定义

typedef struct Node
{
	int data;
	struct Node* next;						//匿名结构体不能进行自引用操作 
}Node;

这里要注意不能省略前面的struct, 因为还没有这种类型不能直接引用Node  ,不能用匿名结构体,匿名结构体不支持解引用。

typedef struct node
{
	int data;
	struct node* next;

}Node;
#include<stdio.h>
#include<stdlib.h>
int main()
{
	Node* p=NULL;
	Node n1, n2, n3;
	n1.data = 10;
	n2.data = 20;
	n3.data = 40;
	n1.next = &n2;
	n2.next = &n3;
	n3.next = NULL;
	for (p = &n1; p != NULL; p = p->next)
	{
		printf("%d ", p->data);
	}
	printf("\n");
	return 0;
}

这样就形成了一个链表,打印第一个 后他的next是&n2,接着去n2打印第二个,n2的next是n3,接着打印n3的,打印完n3next是NULL,停止循环。

3.结构体传参

#include<stdio.h>
struct s
{
	int arr[1000];
	int n;
	double d;
};
void print(struct s p1)
{
	int i = 0;
	for (; i < 5; i++)
	{
		printf("%d ", p1.arr[i]);
	}
	printf("%d ", p1.n);
	printf("%lf ", p1.d);
}
void print1(const struct s* p1)
{
	int i = 0;
	for (; i < 5; i++)
	{
		printf("%d ", p1->arr[i]);
	}
	printf("%d ", p1->n);
	printf("%lf ", p1->d);

}

int main()
{
	struct s s = { {1,2,3,4,5},100,3.14 };
	print(s);   
	print1(&s);  
	return 0;
}

给函数传带有结构体的参数时,有以上两种传参方式,第一种就是传给函数整个结构体,第二种就是把结构体的地址传给函数,而这两种方法看似没什么差别,但是将s完全传给函数,参数需要压栈,有时间和空间上的开销 ,结构体过大时会导致性能下降。而第二种只传一个4bit的地址 效率高。所以以后传给函数结构体类型的变量时,还是传结构体变量的地址就行,能够提高效率,减少性能损耗。

3.结构体的内存对齐

struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{

	struct S1 s1 = { 0 };
	printf("%zd\0", sizeof(s1));

}

上述代码按理说运行结果应该是6个字节,char 1个,int 4个,char 又一个,加起来时6但是最后结果是12,这是因为结构体在内存中的存储不是紧密相连的。

 结构体的内存对齐规则

1.结构体的第一个成员要对齐到偏移量为0的地方(可以理解为结构体开辟的起始地址)。

2.其他成员要对齐到对齐数的整数倍地址处。

对齐数:编辑器默认的对齐数与成员变量的大小中较小的一方。

vs中默认对齐数为8

例如 其中有一个int类型的变量,它的大小是4个字节,默认对齐数为8,4<8,所以此成员变量的对齐数就是4。

3.结构体的总大小是最大的一个对齐数的整数倍

举个例子就是上述代码,第一个元素放到起始地址(偏移量为0),然后第二个是int,int类型的大小是4,vs默认对齐数是8,那么第二个的对齐数就是4(取较小值),要放到偏移量为4的位置,占4个字节。接着char大小是1,默认为8,第三个对齐数就是1,放到8即可。而整体的大小看他们三个中的最大对齐数(4),4过了8以后的整数倍就是12,那么结构体的大小就是12。

4.如果出现了结构体嵌套的情况,嵌套的结构体对齐到本结构体的最大的一个对齐数的整数倍地址处。结构体的整体大小就是最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

struct S3
{
	double d;
	char c;
	int i;
};
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{

	struct S3 s3= { 0 };
	struct S4 s4 = { 0 };

	printf("%zd\0", sizeof(s4));

}

例如上方S4 S3中最大为8 那么放完char ,找到偏移量为8的一位开始存放16个bit(结构体本身大小),存放完到了23接着存放double ,24开始存放,存放到31所有对齐数中最大的就是8 ,那么结构体S4的大小就是32。

对齐方式存在的原因

1.平台(移植)原因

不是所有硬件平台都能访问任意地址的数据的,结构体内存对齐,让每个成员都能处于一个整数倍的位置,有些硬件平台这样可以进行访问。

2.性能原因 

 
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值