C语言:指针的偏移步长、结构体成员的偏移量、嵌套结构体成员的偏移量、结构体的内存对齐


1 不同类型指针的偏移步长

(1)不同类型的指针 +1 时,内存偏移的字节数不同。

char *类型指针 + 1,内存偏移 1 字节;
int *类型指针 + 1,内存偏移 4 字节;
double *类型指针 + 1,内存偏移 8 字节。

示例:

#include <stdio.h>

//1.不同类型的指针+1时,偏移的字节数不同
void func1() {
	char* p = NULL;
	printf("%p\n", p);		//00000000
	printf("%p\n", p + 1);	//00000001

	int* pp = NULL;
	printf("%p\n", pp);		//00000000
	printf("%p\n", pp + 1);	//00000004
}

(2)对不同类型的指针解引用时,取出的字节数不同

示例:

#include <stdio.h>
#include <string.h>

//2.对不同类型的指针解引用时,取出的字节数不同
void func2() {
	char buff[1024] = { 0 };
	int num = 1234;
	//memset(buff, 0, sizeof(buff));
	memcpy(buff, &num, sizeof(num));
	
	char* p = buff;
	//将char*指针强转为int*类型指针后,再解引用
	printf("%d\n", *(int *)p);	//1234
}

//3.对不同类型的指针解引用时,取出的字节数不同
void func3() {
	char buff[1024] = { 0 };
	int num = 1234;
	//memset(buff, 0, sizeof(buff));
	memcpy(buff + 1, &num, sizeof(num));

	char* p = buff;
	//将char*指针强转为int*类型指针后,再解引用
	printf("%d\n", *(int*)(p + 1));	//1234
}

2 结构体成员的偏移量

头文件<stddef.h>定义了标准宏
offsetof()宏可计算结构体成员的偏移量offsetof(结构体类型名 , 结构体成员名)
例:offsetof(struct Object, field);

示例:

#include <stdio.h>
#include <stddef.h>		//使用标准宏

//结构体内存对齐(按数据类型长度最大的对齐-double 8字节对齐)
struct Object {
	char a;			//0 ~ 3
	int b;			//4 ~ 7
	char buff[64];	//8 ~ 71
	double c;		//72 ~ 79
};

int main() {
	struct Object obj = { 'a', 1, "hello world", 3.14 };
	/*
		offsetof()宏可计算结构体成员的偏移量(需包含<stddef.h>头文件)
		offsetof(结构体类型名 , 结构体成员名)
	*/
	printf("%d\n", offsetof(struct Object, a));		//0
	printf("%d\n", offsetof(struct Object, b));		//4
	printf("%d\n", offsetof(struct Object, buff));	//8
	printf("%d\n", offsetof(struct Object, c));		//72

	/* 
		通过结构体成员的指针偏移,可访问各个结构体成员
		将指向结构体的指针强转为(char *)类型,每次指针偏移的步长为1
	*/
	struct Object* p = &obj;
	//char*类型的起始地址,加上结构体成员的偏移量,再强转为指定结构体成员对应的指针类型,解引用获取指定结构体成员的值
	printf("%c\n", *( (char*)p + offsetof(Object, a) ));			//a (ASCII码 97)
	printf("%d\n", *(int *)( (char*)p + offsetof(Object, b) ));		//1
	//打印输出字符串类型时,无需解引用,使用字符串的首地址即可(默认至'\0'结束)
	printf("%s\n", ( (char*)p + offsetof(Object, buff) ));			//hello world
	printf("%lf\n", *(double *)( (char*)p + offsetof(Object, c) ));	//3.140000

	//通过结构体成员的偏移地址,解引用并修改对应结构体成员的值
	*(double *)((char*)p + offsetof(struct Object, c)) = 3.1415926;	//修改结构体成员c的值
	printf("%lf\n", *(double *)((char*)p + offsetof(Object, c)));	//3.141593

	return 0;
}

3 嵌套结构体成员的偏移量

#include <stdio.h>
#include <stddef.h>

struct Inner {
    char a;
    int b;
};

struct Outer {
    char c;
    int d;
    struct Inner inner;
};

int main() {
    struct Outer outer = { 'a', 1, {'b', 2} };

    /* 获取Outer结构体中嵌套的Inner结构体成员b */
    //1.通过结构体变量访问
    printf("%d\n", outer.inner.b);  //2

    //2.通过2次指针偏移:
    //(1)先获取嵌套结构体成员inner相对outer的偏移
    int offset1 = offsetof(struct Outer, inner);
    //(2)再获取属性b相对结构体inner的偏移
    int offset2 = offsetof(struct Inner, b);
    printf("%d\n", *(int*)((char*)&outer + offset1 + offset2));     //2

    //3.通过1次指针偏移,获取指向嵌套结构体inner的指针,通过结构体指针访问成员
    printf("%d\n", ((struct Inner*)((char*)&outer + offset1))->b);  //2

    return 0;
}

4 结构体的内存对齐

4.1 内存对齐的原因与优点

内存未对齐的问题:CPU按块读取内存,当CPU访问数据时,若内存未对齐,则可能导致二次访问的情况,即前后两次访问的数据需拼接后才能获取指定数据。

内存对齐的优点以空间换时间,浪费部分用于对齐的空间,通过一次访问即可获取指定数据。


4.2 结构体内存对齐的规则

(1)内置数据类型:数据存储在该类型大小的整数倍上。

(2)自定义数据类型:依照特定的对齐规则。
①从第1个属性开始,从 偏移量0位置 开始存储;
②从第2个属性开始,从 该数据类型大小对齐模数较小值的整数倍开始存储。即min {该数据类型大小 , 对齐模数} 的整数倍
③整体计算完毕后进行二次对齐,结构体的总大小必须是 该结构体中最大数据类型对齐模数较小值的整数倍不足需补齐。即min {该结构体中最大数据类型 , 对齐模数} 的整数倍

注:使用#pragma pack(show)生成代码后查看”杂注”即默认对齐模数。默认对齐模数为8。
例:pragma pack(show) 的值 == 8

示例1:按默认对齐模数(8)对齐

#include <stdio.h>
//#pragma pack(show)	//对齐模数默认为8	//生成后查看

typedef struct {
	int a;		//0 ~ 3
	char b;		//4 → 4 ~ 7				//double对齐后,补为4 ~ 7
	double c;	//8 ~ 15
	float d;	//16 ~ 19 → 16 ~ 23		//二次对齐,补为16 ~ 23
} StructA;

int main() {
	printf("%d\n", sizeof(StructA));	//24

	return 0;
}

示例2:按指定对齐模数(1)对齐

#include <stdio.h>
#pragma pack(1)	//对齐模数为1

typedef struct {
	int a;		//0 ~ 3
	char b;		//4			//对齐模数为1,无需补齐
	double c;	//5 ~ 12
	float d;	//13 ~ 16	//二次对齐,对齐模数为1,无需补齐
} StructB;

int main() {
	printf("%d\n", sizeof(StructB));	//17

	return 0;
}

4.3 结构体嵌套结构体时的对齐规则

①从第1个属性开始,从 偏移量0位置 开始存储;
②从第2个属性开始的非嵌套结构体属性,从 该数据类型大小对齐模数较小值的整数倍开始存储。即min {该数据类型大小 , 对齐模数} 的整数倍
③从第2个属性开始的嵌套结构体属性,从 该嵌套结构体中最大数据类型对齐模数较小值的整数倍开始存储。即min {该嵌套结构体中最大数据类型 , 对齐模数} 的整数倍
④整体计算完毕后进行二次对齐,结构体的总大小必须是 该结构体中最大数据类型对齐模数较小值的整数倍不足需补齐。即min {该结构体中最大数据类型 , 对齐模数} 的整数倍

示例:结构体嵌套结构体时,按默认对齐模数(8)对齐

#include <stdio.h>
#include <stddef.h>

//默认对齐模数为8
typedef struct {
	int a;		//0 ~ 3
	char b;		//4 → 4 ~ 7				//double对齐后,补为4 ~ 7
	double c;	//8 ~ 15
	float d;	//16 ~ 19 → 16 ~ 23		//二次对齐,补为16 ~ 23
} Inner;		//对齐模数为8时,二次对齐后,Inner结构体的大小为24

typedef struct {
	int x;		 //0 ~ 3
	char y;		 //4 → 4 ~ 7  //嵌套结构体inner对齐后,补为4 ~ 7
	Inner inner; //8 ~ 31	  //按嵌套结构体inner的最大数据类型(8)和对齐模数 对齐
	float z;	 //32 ~ 35 → 32 ~ 39	//二次对齐,补为32 ~ 39
} Outer;		 //对齐模数为8时,二次对齐后,Outer结构体的大小为40


int main() {
	printf("内层结构体大小:%d\n", sizeof(Inner));	//24
	printf("外层结构体大小:%d\n", sizeof(Outer));	//40

	printf("属性x的偏移:%d\n", offsetof(Outer , x));			//0
	printf("属性y的偏移:%d\n", offsetof(Outer , y));			//4
	printf("属性inner的偏移:%d\n", offsetof(Outer , inner));	//8
	printf("属性z的偏移:%d\n", offsetof(Outer , z));			//32

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值