C语言 结构体 | 由浅到深介绍

一、结构体的概述

C语言中有两种类型:原生类型和自定义类型。结构体类型是一种自定义类型

1、结构体使用

定义结构体是需要先声明结构体的类型,然后再用结构体类型来定义结构体变量,不过也可以在定义的同时定义结构体变量。如:

# include<stdio.h>
//定义类型
struct people
{
	char name[20];
	int age;
}//定义类型的同时定义变量
struct student 
{
	char name[20];
	int age;
}s1;
//将类型struct student 重命名s1 ,s1是一个类型名,不是变量
typedef struct student 
{
	char name[20];
	int age;
}s1;
int main(void)
{
	struct people p1;     //使用结构体类型定义变量
	p1.age  = 20;
	printf (“p1.age = %d \n”,p1.age);
	return 0;
}

2、从数组到结构体的进步之处

结构体可以认为是从数组发展而来的,其实数组和结构体都算是数据结构的范畴了,数组就是最简单的数据结构;结构体比数组更复杂一些;链表,哈希表之类的比结构体有复杂一些;而二叉树、图又更复杂一些。
数组有两个明显的缺陷:

  • 第一个是定义时必须明确给出大小,且这个大小在以后不能修改;
  • 第二个是数组要求所有的元素类型必须一致。

在更加复杂的数据结构中,就致力于解决数组的这两个缺陷。结构体是用来解决数组的第二个缺陷的,可以将结构体理解为其中元素类型不相同的数组。结构体完全可以取代数组,只是在通常简单的情况下,数组使用起来更为简单方便。

3、结构体变量中的元素如何访问

数组元素的访问方式,表面上看有两种:下标方式和指针方式。但实质上都是指针方式。结构体变量中元素的访问方式只有一种,用句号.或者箭头->的方式访问。这两种访问结构体元素的实质是一样的,当使用指针的时候完全可以使用句号访问,只是写法复杂,可行性不强,因此就使用箭头来代替。使得访问形式看起来更加简洁。结构体对于成员的访问本质上还是使用地址进行访问。

二、结构体的对齐访问

上面讲过结构体中元素的访问,本质上使用的还是指针方式,结合这个元素在整个结构体中的偏移量和这个元素的类型来访问。但是实际上结构体的元素的偏移量比我们想想的还要复杂,因为结构体要考虑元素的对其访问,结构体实际占用的字节数与所有成员的字节数的总和不一定相等。

1、结构体为何要对其访问

访问结构体元素时需要对其访问,主要是为了配合硬件,也就是说硬件身物理上的限制,因此对其排布和访问可以提高访问效率。

struct student
{ 
	char name[20];
	int age;
};

内存本身是一个物理器件(DDR内存芯片,soc上的DDR控制器),本身有一定的局限性,如果内存每次访问按照4字节访问,那么效率是最高的;如果不对齐访问,效率要低很多。
还有很多别的因素和原因,导致我们需要对其访问。如:cache 的一些缓存特性,还有其他硬件(如MMU、LCD显示器)的一些内存依赖特性,所以会要求内存对齐访问。

2、结构体对齐的规则和运算

编译器本身可以设置内存对齐的规则。32位编译器,一般编译器默认对齐方式是4字节对齐。
1、当编译器结构体设置为4字节对齐时,结构体整体必须从4字节对齐处存放,结构体对齐后的大小必须为4的倍数。如果编译器设置为8字节对齐,则这里的4就是8.
2、结构体中每个元素本身也必须对齐存放。
3、编译器在考虑以上两点的情况下,实现以最少内存来开辟结构体空间。

3、手动对齐

使用 #pragma 进行对齐的就是手动对齐
#pragma用于告诉编译器,程序员自己希望的对齐方式,比如,虽然编译器默认的对齐方式是4字节对齐,但是我们不希望4字节对齐,而是希望实现别的对齐方式,如1字节对齐、8字节对齐,128字节对齐,这个时候就必须使用#pragma进行手动对齐。
常见的设置手动对齐的命令有两种。

  • 第一种#pragma pack(),这种就是设置编译器1字节对齐,不过也可以认为是设置为不对齐或者取消对齐。
  • 第二种是#pragma pack(4),这个括号中的数字表示希望以多少字节对齐。
    我们需要以#pragma pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对齐参数就是n.
#include <stdio.h>
#pragma pack(4)

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

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

struct struct3
{	
	int a;
	struct struct1 s1;
	double b;
	int c;
}Mys3;
struct stu
{
	char sex;
	int length;
	char name[10];

};
#pragma pack()
int main(void)
{
	printf("sizeof(struct struct1) = %d \n",sizeof(struct struct1));
	printf("sizeof(struct struct2) = %d \n",sizeof(struct struct2));
	printf("sizeof(struct struct3) = %d \n",sizeof(Mys3));
	printf("sizeof(struct stu) = %d \n",sizeof(struct stu));
	return 0;
}

1
对于 #pragma pack手动对齐来说,在很多C语言环境下都是支持的自然GCC也支持,如果不是有特殊需求的话,不建议使用。

4、GCC推荐的对齐指令:

  • attribute((packed))和_attribute_((aligned(2)))
    使用_attribute_((packed))和_attribute_((aligned(2)))时,直接放在类型定义的后面,那么该类型就以指定的方式进行对齐。packed的作用就是取消对齐,aligned(n)表示对齐方式
#include <stdio.h>
struct struct1
{
	int a;
	char b;
	short c;
}_attribute_((packed));

struct struct2
{
	char a;
	int b;
	short c;
}_attribute_((packed));

int main(void)
{
	printf("sizeof(struct struct1) = %d \n",sizeof(struct struct1));
	printf("sizeof(struct struct2) = %d \n",sizeof(struct struct2));
	return 0;
}
#include <stdio.h>
struct struct1
{
	int a;
	char b;
	short c;
}_attribute_((aligned(n)));

struct struct2
{
	char a;
	int b;
	short c;
}_attribute_((aligned(1024)))Mystruct1;

int main(void)
{
	printf("sizeof(struct struct1) = %d \n",sizeof(struct struct1));
	printf("sizeof(struct struct2) = %d \n",sizeof(struct struct2));
	return 0;
}
  • _ attribute_((aligned(n)))他的作用是让结构体类在整体上按照n字节对齐。

三、offsetof宏与container_of宏

1、通过结构体指针访问各结构体成员的原理

通过结构体变量来访问其中各个元素,本质上是通过指针的方式来访问的,形式上是句点.的方式来访问的(这时候其实编译器帮助我们自动计算了偏移量)。

(1)、offsetof宏

offsetof宏的作用:
计算结构中某个元素相对结构体首字节地址的偏移量,其实质是通过编译器来帮我们计算的。

#include<stdio.h>
#define offsetof(TYPE,MEMBER)     ((int) & ((TYPE*)0)->MEMBER)
struct Mystruct
{
	char a;
	int b;
	short c;
};
/*
	TYPE是结构体类型,MEMBER是结构体中一个元素的元素名
	这个宏返回的是MEMBER元素相对整个结构体变量的首地址
	的偏移量,类型是int。
*/
int main(void)
{
	struct Mystruct s1;
	s1.b = 12;
	int *P = (int *)((char*)&s1 + 4);
	printf("*p = %d\n",*p);//这种方法是自己根据结构体对齐计算得出的。
	return 0;
}

offsetof宏的原理:
我们虚拟一个TYPE类型的结构体变量,然后用TYPE,MEMBER的方式来访问MEMBER元素,继而得到MEMBER相对整个变量首地址的偏移量。
简单说明:(TYPE *)0是一个强制类型转换,把0 地址强制转换成一个指针,这个指针指向一个一个TYPE类型的结构体变量,实际上这个结构体变量可能不存在,但是只要不去解引用这个指针就不会出错。

(3)、container_of宏

#define container_of(ptr,type,member)(\
{const typeof(((type* )0)->member) *_mptr = (ptr); \
(type *)((char *)_mptr - offsetof(type,member);)
})
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值