引言:先观其大略再逐步深入,这是笔者的创作初衷也是其学习感悟!
文章向导
- struct出场的原由
- 结构声明、结构变量及初始化
- 结构指针(指向结构的指针)
- 结构体与柔性数组(C99新成员)
- 结构体对齐问题
正文
一、struct出场的原由
在程序设计中最重要的一个问题就是如何有效地表示数据,而单纯地使用简单的变量或数组都是不够的,所幸的是C语言提供的struct关键字为人们打开了一扇新的大门。
二、结构声明、结构变量及初始化
1.结构声明和结构变量
struct test
{
char name;
char class;
float value;
};
struct test T; //编译器使用test模板定义结构变量T
如上所示的代码片段就为一种结构声明,该声明描述了一个由两个字符变量和一个float变量组成的结构,但它并没有创建一个实际的数据对象,仅仅只是描述组成这类对象的元素。有时候,结构声明也被称为模板,不过这并不是C++中所谈及的模板概念。
2.如何初始化一个结构变量?
想对一结构变量进行初始化,可使用与初始化数组相似的方式(以上文中的结构变量T为例):
struct test T = {
'A',
'2',
2.00
};
不过采用这种方式进行初始化时,需注意初始化的项目要和初始化的结构成员类型相匹配,当然也可以使用如下这种任意顺序的初始化方法:
/*使用点运算符和成员名来进行初始化*/
struct test T = {
.value = 2.00,
.name = 'a',
.class = '2'
};
3.结构声明的几种形式
使用结构体时,不免会碰见其各种形式的写法,现对此作一番梳理:
struct A //第一种,最基本的形式
{
int a;
};
struct B //第二种,在声明结构B的同时定义了一个使用该结构的变量m
{
int b;
} m;
struct //第三种,没有给出该结构的标记,但定义了一个结构变量n
{
int c;
} n;
/* 第四种,加上typedef关键字后,可将struct D{int d;} 看成是一个数据类型,故用struct D 定义变量即可。*/
typedef struct D
{
int d;
};
/* 第五种,在第四种的基础上加上了别名x,故此时可以直接使用x定义结构变量。*/
typedef struct E
{
int e;
} x;
/* 第六种,在第五种的基础上删掉了结构标记,但还是可以直接使用别名y来定义结构变量。*/
typedef struct
{
int f;
} y;
三、结构指针(指向结构的指针)
程序设计者应该都知道指向数组的指针比数组本身更容易操作(如在一个排序问题中),那么类比分析可知指向结构的指针也应比结构本身更容易操作,有了这一前提认识也就更加能意识到学习结构指针的重要性。
typedef struct
{
int init;
char field;
} my_struct;
int main(void)
{
my_struct * s; //定义结构指针s
s = (my_struct *)malloc (sizeof(my_struct));
s->init = 4; //使用间接成员运算符访问成员
(*s).field = 'a'; //使用.运算符访问成员,注:.运算符比*的优先级更高,故()不能少
printf("%d\n", s->init);
printf("%c\n", s->field);
return 0;
}
上面这个例子就是如何定义并使用一个结构指针,其中值得注意的就是访问其结构成员的两种方式。
四、结构体与柔性数组
1.柔性数组
柔性数组在C99中也称之为伸缩型数组,仅从名称来看并不能很好地理解其妙用,在正式介绍其用法之前,首先看看声明一个柔性数组的规则:
struct SoftArray
{
int len;
int array[]; //柔性数组
};
int main()
{
printf("sizeof(struct SoftArray)=%d\n",sizeof(struct SoftArray)); //输出结果为4
return 0;
}
从此程序片段不仅了解到如何声明一个柔性数组,同时还能发现柔性数组的一个特性,也就是不立即存在的特性(因打印结果为4)。
2.柔性数组的用法
谈到如何使用一个柔性数组,那么就不得不牵涉到之前所提及的结构指针。接下来,请看一个完整的例子就能深刻体会柔性数组的妙用!
#include
#include
typedef struct SoftArray
{
int len;
int array[];
} my_SoftArray;
my_SoftArray* creat_soft_array(int size)
{
my_SoftArray * ret = NULL;
if(size>0)
{
ret = (my_SoftArray*)malloc(sizeof(my_SoftArray)+sizeof(int)*size); //注意这一句
ret->len = size;
}
return ret;
}
void delete_soft_array(my_SoftArray** sa)
{
free(*sa);
*sa = NULL;
}
/*给柔性数组中的每个元素赋值*/
void func(my_SoftArray* sa)
{
int i = 0;
if(NULL != sa)
{
for(i=0; i<sa->len; i++)
{
sa->array[i] = i+1;
}
}
}
int main()
{
int i = 0;
my_SoftArray* sa = creat_soft_array(10); //初始化结构指针
func(sa); //使用柔性数组
for(i=0; i<sa->len; i++)
{
printf("%d\n",sa->array[i]);
}
delete_soft_array(&sa); //释放操作柔性数组的结构指针
return 0;
}
现具体分析柔性数组的“柔”,为便于理解则摘取上述的程序片段:
typedef struct SoftArray
{
int len;
int array[];
} my_SoftArray;
my_SoftArray* sa = NULL;
sa = (my_SoftArray*)malloc(sizeof(my_SoftArray) + sizeof(int)*size);
sa->len = size;
这里不妨假设size=5,那么malloc请求申请的堆空间应为sizeof(my_SoftArray)=4 + sizeof(int)*size=20 ==> 24字节数,具体的内存布局如下图所示:
由上图清晰可知,sizeof(my_SoftArray)得到的空间用于存放len,而sizeof(int)*size得到的空间则用于存放柔性数组的内容,同时柔性数组的长度信息已由len所指定。换句话说,柔性数组在声明时并不知其具体大小,而是在使用时通过结构指针来动态指定的。
五、结构体对齐问题
结构体对齐也称之为内存对齐,它说的是不同类型的数据在内存中按照一定规则进行排序,而不一定是一个接一个的顺序排序。研究此问题的意义在于,不仅是为了更好地使用结构体,同时在笔试/面试题中也会碰见此类问题的身影。
struct test1 struct test2
{ {
char c1; char c1;
short s; char c2;
char c2; short s;
int i; int i;
}; };
现以上面这两个结构为例,两者占用的内存空间是相同的吗?咋一看,除了成员顺序不同外,两结构基本没什么差异,但用编译器检验可知前者占用12个字节,后者占用8个字节。好吧,直观上这确实让人很难以理解,下面从内存布局的角度来进行一番分析:
上图呈现出两结构在内存中的存储差异,但不免就引出了两个问题:
Q1:为什么需要内存对齐?
Q2:这样的对齐方式是如何计算的?
首先,回答第一个问题,即为何需要内存对齐。
接着,回答第二个问题,即如何计算内存对齐。
此时就要引出一位新成员#pramga pack指令,它用于指定内存对齐的方式(按指定的字节数进行对齐),在正式举例之前,先看看其使用规则:
struct占用的内存大小(重要规则)
结构中的第一个成员位于0偏移地址处
每个成员按其类型大小和pack参数中较小的一个进行对齐(对齐参数的选定)
偏移地址必须能被对齐参数整除
结构成员的大小取其内部长度最大的数据成员作为其大小(适用于结构嵌套的情形)
结构总长度必须为所有对齐参数的整数倍
编译器在默认情况下按照4字节对齐,即#pragma pack(4)。
实例分析
#pragma pack(4)
struct test1
{ //对齐参数 偏移地址 大小
char c1; // 1 0 1
short s; // 2 2 2
char c2; // 1 4 1
int i; // 4 8 4
}
#pragma pack()
#pragma pack(4)
struct test2
{ //对齐参数 偏移地址 大小
char c1; // 1 0 1
char c2; // 1 1 1
short s; // 2 2 2
int i; // 4 4 4
}
#pragma pack()
Tips:结构占用内存大小 = (末行)(偏移地址+大小)
该例子展示了如何使用上述提到的规则进行计算,以及如何使用#pragma pack指定内存对齐方式,读者可对照之前的内存布局图自行验证。
参阅资料
C primer Plus
狄泰软件学院- C语言进阶剖析教程
C语言经典编程282例
本文也是笔者的第一篇文章,水平和学识有限,希望各位读者能多多交流、批评指正。