(c语言)结构体内容详解

本文详细介绍了C语言中结构体的基础概念,包括结构体定义、内存对齐规则、结构体传参策略以及位段的使用。讨论了内存对齐的必要性,指出结构体传参的性能影响,并强调了位段的节省空间特性及其跨平台问题。
摘要由CSDN通过智能技术生成

目录

一:结构体基础概念

1.什么是结构体?

2.结构体的定义与声明

3.结构体的特殊声明:

4.结构体变量的初始化:

5.结构体成员的访问:

二:结构体内存对齐

1.什么是内存对齐?

2.内存对齐详解:

3.为什么存在内存对齐?

三.结构体传参

四.结构体实现位段

1.什么是结构体位段?

2.位段在内存中的存储

3.位段的跨平台问题


一:结构体基础概念

1.什么是结构体?

结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员” ,其描述了一块内存区间的大小及解释意义 。

2.结构体的定义与声明

定义:结构体的定义如下所示,struct为结构体关键字,tag为结构体的名字,member-list为结构体成员列表,其必须列出其所有成员;variable-list为此结构体声明的变量。

 struct tag {
 member-list
 } variable-list ; 

声明:

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签

struct {

    int a;

    char b;

    double c;

} s1;


//同上声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE{

    int a;

    char b;

    double c;

};

在上面的声明中,第一个和第二个声明被编译器当作两个完全不同的类型,一个是struct一个struct SIMPLE,因此即使他们的成员列表是一样的,也是不同的。如果令t3=&s1,则是非法的。

3.结构体的特殊声明:

匿名结构体声明:

* 在声明结构的时候,可以不完全声明,例如:

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;

struct
{
 int a;
 char b;
 float c;
}a[20], *p;

上⾯的两个结构在声明的时候省略掉了结构体标签(tag)。

此时编译器就会把上面两个结构体当成完全不同的连个类型,p=&x也是非法的。并且匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。

* 结构体类型的重命名:

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

typedef可以把原来的结构体名字进行重命名。在这个代码中就是将struct Node重命名为Node,重命名前定义变量a需要写struct Node a,重命名后只需要写Node a即可,更加简便。

4.结构体变量的初始化:

#include <stdio.h>
struct book
{
 char name[20];//名字 
 int prince;//价格
 char id[20];//书单号
};
int main()
{
 //按照结构体成员的顺序初始化 
 struct Stu book1 = { "红楼梦", 30, "1234" };

 //按照指定的顺序初始化 
 struct Stu book2= { .price = 18, .name = "红楼梦", .id = "1234"};

 return 0;

}
 

5.结构体成员的访问:

用"点"运算符进行访问(变量名+点+成员名):

#include <stdio.h>
struct book
{
 char name[20];//名字 
 int price;//价格
 char id[20];//书单号
};
int main()
{
 //按照结构体成员的顺序初始化 
 struct Stu book1 = { "红楼梦", 30, "1234" };
 printf("%s",book1.name);
 printf("%d",book1.price);
 printf("%s",book1.id);
 return 0;
}

用结构体指针进行访问:

#include <stdio.h>
struct book
{
 char name[20];//名字 
 int price;//价格
 char id[20];//书单号
};
int main()
{
 //按照结构体成员的顺序初始化 
 struct book a1={"红楼梦",20,"abc12345"};
 struct Stu *book1 = &a1;
 printf("%s",book1->name);
 printf("%d",book1->price);
 printf("%s",book1->id);
 return 0;
}

二:结构体内存对齐

1.什么是内存对齐?

结构体内存对齐是指在创建结构体变量时,其成员在内存中的存储遵循特定的规则,以确保数据内容正确且高效地存储和访问。

2.内存对齐详解:

结构体内存对齐遵循一定的规则:

  • 第一个成员变量总是存储在结构体变量存储起始位置的偏移量为0的地址处。
  • 从第二个成员开始,每个成员变量必须存储在其自身对齐数的整数倍地址处。对齐数取决于编译器默认的对齐数和成员大小中的较小值,vs中默认对齐数为8。
  • 结构体的总大小必须是其成员中最大对齐数的整数倍。这意味着结构体的大小可能会因为对齐需求而大于所有成员大小的总和。
  • 当结构体中嵌套有结构体时,嵌套的结构体及其成员必须按照上述规则对齐。嵌套结构体的对齐到其自身成员最大对齐数的整数倍处,而整个结构体的大小则是所有最大对齐数的整数倍。

这里添加一个代码:

struct S2
{
 char c1;
 char c2;
 int i;
};
printf("%d\n", sizeof(struct S2));

来通过上面的四条规则来计算结构体类型的大小。

        如图所示:

  图中最上面的横线是结构体内存中的起始位置,也是偏移量为0的地址,偏移量就是结构体内存中变量相对于偏移的大小。

根据规则一,c1应该存在偏移量为0的地址,且c1为char类型因此占一个字节,即第一个格子放c1。

接下来存c2,根据规则二,每个成员变量必须存储在其自身对齐数的整数倍地址处,假设存在vs中,应该存c2大小的整数倍地址处,即1的整数倍地址,已知c1存在偏移量为1的地址处,那么c2应该接着c1存在偏移量为1的地址处。

接着存i,根据规则2,因为i的大小为4个字节,比8小,因此对齐数为4,因此存在4的整数倍地址处,因此接着c2存,跳过6个字节,存在偏移量为8的地址处,因此i在地址中占的是偏移量8—11的位置。

最后,根据规则三,结构体的总大小必须是其成员中最大对齐数的整数倍。即4的整数倍。由于此时结构体内三个成员在内存中占用的是0—11字节的空间,共占用12个字节,是4的整数倍,所以12个字节也是这个结构体的大小。

3.为什么存在内存对齐?

1. 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常。

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

总体来说:结构体的内存对⻬是拿空间来换取时间的做法。

三.结构体传参

struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参 
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参 
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s); //传结构体 
 print2(&s); //传地址 
 return 0;
}

在上面传参中,printf1传地址的传参形式比printf2传结构体的形式要好。

因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降,而传输地址则避免了这种情况。

四.结构体实现位段

1.什么是结构体位段?

*位段是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体(联合体)称为位段结构。采用位段结构既能够节省空间,又方便于操作。

 *位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以 选择其他类型。

 *位段的成员名后边有⼀个冒号和⼀个数字

例如:

struct A
{
 int a:2;
 int b:5;
 int c:10;
 int d:30;
};

A就是一个位段类型。那位段A所占的空间是多少呢?

2.位段在内存中的存储

*位段的成员可以是 int ,unsigned int, signed int 或者是 char 等类型

*位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。

注意:位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。

这里举一个在vs中位段存储的例子:

struct S
{
 char a:3;
 char b:4;
 char c:5;
 char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

在这个代码中,内存是如何开辟空间来存储数据呢?

由于结构体中都是char类型位段成员,因此位段的空间是一个字节开辟的,一个字节不够用,再开辟一个字节。

10 存入char a中由于字节限制只存入了后8个bit位:00001010,又因为位段限制,只能存入三个bit位,即只存入010。

12存入char a中只存入00001100,因为位段限制,只存入1100。

此时,这一个字节的内存空间存入了7个bit位,还剩一个没有填充,则直接填充0,再开辟一个字节的空间供接下来的位段成员存储。

因此把a、b、c、d全部存储进位段中,需要3个字节的空间。可以用下图来表示:

3.位段的跨平台问题

1. 位段被当成有符号数还是⽆符号数是不确定的。

 2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。 4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。

总结: 跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

  • 31
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vc++全版本组件大全 VC++运行时(Visual C++ Runtime)是VC++开发环境中用于支持C和C++程序运行的基础库集合。这些库包含了执行C/C++程序所必需的基本函数和数据结构,例如内存管理、字符串操作、输入输出处理、异常处理等。VC++运行时库分为静态库和动态库两种形式,以适应不同类型的项目需求。 静态链接库 vs 动态链接库 静态链接库(Static Linking Libraries):在编译时,静态库的代码会被直接嵌入到最终生成的可执行文件中。这意味着每个使用静态库的程序都会包含库代码的一个副本,导致最终程序的体积较大,但不需要外部库文件支持即可独立运行。在VC++中,静态链接库的例子有LIBC.lib(用于单线程程序)和LIBCMT.lib(用于多线程程序)。 动态链接库(Dynamic Link Libraries):与静态链接相反,动态库的代码并不直接加入到应用程序中,而是在程序运行时被加载。这使得多个程序可以共享同一份库代码,节省了系统资源。VC++的动态运行时库主要通过msvcrt.dll(或其变体,如MSVCRTD.dll用于调试版本)实现,与之配套的导入库(Import Library)如CRTDLL.lib用于链接阶段。 运行时库的版本 VC++运行时库随着Visual Studio版本的更新而发展,每个版本都可能引入新的特性和优化,同时保持向后兼容性。例如,有VC++ 2005、2008、2010直至2019等多个版本的运行时库,每个版本都对应着特定的开发环境和Windows操作系统。 重要性 VC++运行时对于确保程序正确运行至关重要。当程序在没有安装相应运行时库的计算机上执行时,可能会遇到因缺失DLL文件(如MSVCP*.dll, VCRUNTIME*.dll等)而导致的错误。因此,开发完成后,通常需要分发相应的VC++ Redistributable Packages给最终用户安装,以确保程序能够在目标系统上顺利运行。 安装与部署 安装VC++运行时库通常是通过Microsoft提供的Redistributable Packages完成的,这是一个简单的过程,用户只需运行安装程序即可自动安装所需组件。对于开发者而言,了解和管理不同版本的运行时库对于确保应用程序的广泛兼容性和可靠性是必要的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值