C语言基础 -- 结构体变量及其成员内存分配

C语言中的结构体是一个在实际编程中很有用的数据结构,也是很多C语言面试题目喜欢考察的知识点。我们下面就详解介绍一下结构体,特别是结构体变量成员的内存分配问题。

零、前言

对于不了解结构体的同学,首先需要搞清楚两个概念:一是结构体类型、二是结构体变量。

结构体类型类似与我们知道数据类型(例如:int)是用来声明变量的,它同时规定了该类型的变量所应占用的内存的大小。结构体变量是一个实实在在的变量,它在内存中有地址,并且占用了由声明它时用到的结构体类型所规定的内存大小。请对比下面两个例子:

例1:

#include <stdio.h>
int main () {
	int N;
	printf("%d %d %d\n", &N, sizeof N, sizeof(int));
	return 0;
}

输出1:

6422044 4 4

例1中,int是数据类型,int规定了用它声明的变量的内存大小,即sizeof(int)。N是变量,它有地址,即&N;它所占的内存有大小,即sizeof N。

例2:

#include <stdio.h>
struct Item {
	char a;
	int b;
	char c[9];
	double d;
};

int main () {
	struct Item item;
	printf("%d %d %d %d\n", &item, &item.a, sizeof item, sizeof(Item));
	return 0;
}

输出2:

6422016 6422016 32 32

例2中,Item是结构体类型,Item规定了用它声明的结构体变量的内存大小,即sizeof(Item)。item是结构体变量,它有地址,即&item;它所占的内存有带小,即sizeof item。这里与数组有些类似,结构体变量的地址就是该变量第一个成员的地址。

为了叙述简便,下面提到“结构体”的地方一般都是值结构体变量。

一、结构体与数组的比较

(必须说明在先,结构体变量和数组变量本来就是两种不同类型的变量,无所谓比较异同。这里硬生生的比较是为了突出结构体的成员的内存分配会和我们想想中的不太一样,为后面的重点作一个铺垫。)

先说相同的点:数组变量和结构体变量都是占据了一大块内存。数组的每一个元素占据整个数组内存的一部分;同样结构体变量的每一个成员占据整个结构体内存的一部分。

再说不同的点:整个数组所占据的内存就等于每个元素占据内存的总和,而整个结构体占据的内存不一定等于每个成员占据的内存的总和;或者或每个数组元素所占据的内存是前后紧密连接的,而结构体成员所占据的内存不一定前后紧密连接的,中间可能会有间隔。

下面我们就详细讲解,给定一个结构体变量,如何确定每个成员所占据的内存的位置和整个结构体变量所占据的总的内存。

二、结构体变量的内存分布

我们先看一个例子,这个例子可以说明结构体成员的内存并不是连续的。

例3:

#include <stdio.h>
struct Item {
	char a;
	int b;
	char c[9];
	double d;
};

int main () {
	struct Item item;
	printf("%d %d %d %d\n", sizeof(item.a), sizeof(item.b), sizeof(item.c), sizeof(item.d));
	printf("%d %d %d %d\n", &item.a, &item.b, &item.c, &item.d);
	return 0;
}

输出3:

1 4 9 8
6422016 6422020 6422024 6422040

上面代码运行后第一行输出了每个成员所占据内存的大小,第二行输出了每个成员的地址。可以看到item.a的地址是6422016,item.a占据1个字节内存,那么6422017以后的内存就没有被item.a所占据。但是分配给item.b内存时,却没有从6422017开始,而是从6422020开始。6422017、6422018、6422019这3个字节的内存直接上就被浪费掉了,C语言这样做的原因是为了节省访问成员内存的时间。这种分配的原则可以称为“内存对齐”+“按顺序分配”

“内存对齐”是指:每个成员占据的内存只能是从起始位置(也就是整个结构体的收i地址)开始的和自己的数据类型所占据的内存的整数倍对齐的位置。

“按顺序分配”是指:按照定义结构体类型时成员的先后顺序给结构体变量的成员分配内存。

下面我们用一张图来解释。

这段内存如下图所示,每一个小蓝格代表1个字节的内存,起始位置是6422016。如果一个成员a的数据类型为char,因为char占一个字节,所以给a分配的内存可以与红色绿色蓝色的虚线对齐;如果一个成员b的数据类型是int,因为int占据4个字节,所以给b分配的内存只能与红色绿色的虚线对齐;如果一个成员d的数据类型是double,因为double占8个字节,所以给d分配的内存只能与红色的虚线对齐。

于是,按照例3中结构体类型Item的成员定义的顺序,在申明结构体变量item时,先给item.a分配内存,分配一个字节,即6422016;然后给item.b分配内存,分配4个字节,因为6422016被占据了,所以只能从6422020开始;然后给item.c分配内存,分配9个字节,注意虽然item.c占9个字节,但是item.c是数组,它的数据类型还是char,占一个字节,所以分配给它从6422024开始的9个字节;最后给item.d分配内存,占8个字节,由于6422032被占据了,所以只能从6422040开始。

以上就是例3中结构体item的内存分配过程。

三、间接引用结构体成员的方法

我们知道假设一个结构体变量itme有一个成员a,那么引用该成员的语法就是item.a,当然也可以用地址间接引用到该成员。

例4:

#include <stdio.h>
struct Item {
	char a;
	int b;
	char c[9];
	double d;
};

int main () {
	struct Item item = {'M',21,"frank",1.75};
	char *p = (char*)&item;
	printf("%c %d %s %f\n", item.a, item.b, item.c, item.d);
	printf("%c %d %s %f\n", *p, *(int*)(p+4), p+8, *(double*)(p+24));
	return 0;
}

输出4:

M 21 frank 1.750000
M 21 frank 1.750000

上面的代码中第一个printf语句中是直接引用成员,第二个printf语句中是间接引用成员,二者效果是一样的。

猜你喜欢

转载自blog.csdn.net/morn_l/article/details/134660968
今日推荐