c语言进阶部分详解(详细解析自定义类型——结构体,内存对齐,位段)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: c语言进阶部分详解(详细解析自定义类型——结构体,内存对齐,位段)

今天要介绍的是:结构体的相关内容


一.结构体类型的声明

1.结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量

2.结构的声明

结构的声明的原型:

struct tag

{

   member-list;

}variable-list;

eg:

struct Student
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

3.特殊的声明

匿名结构体:

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

4.结构的自引用

我们也许回想在结构中包含一个类型为该结构本身的成员是否可以呢?像下面这样

struct Node
{
  struct Node a1;
  int date[2];
};

这种情况下,需要确保结构体类型的定义是在使用它的结构体类型之前。否则,编译器将无法确定结构体类型的大小。

所以是不行的,正确的自引用方式如下:

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

注意:

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

在定义指针变量next时,使用了Node类型。由于Node类型的定义在当前代码中尚未完成,编译器无法识别Node类型

正确的如下:

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


5.结构体变量的定义和初始化

struct Point
{
 int x;
 int y;
}p1; //声明结构体的同时定义变量p1
struct Point p2; //定义结构体变量p2
//也可以
struct Point p3 = {x, y};//初始化:定义变量的同时赋初值。
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //声明结构体的同时嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//单独结构体嵌套初始化

6.结构体传参

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);
  printf("%d\n", s.data[0]);
  printf("%d\n", s.data[1]);
  printf("%d\n", s.data[2]);
}
//结构体地址传参
void print2(struct S* ps)
{
  printf("%d\n", ps->num);
  printf("%d\n", ps->data[0]);
  printf("%d\n", ps->data[1]);
  printf("%d\n", ps->data[2]);
}
int main()
{
  print1(s); //传结构体变量
  printf("_________");
  print2(&s); //传地址
  return 0;
}

两种调用,传参的结果都是一样的:

但是,还是用printf2来传地址时更好:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降


二.结构体内存对齐

struct S1

{

    char c1 ;

    int i ;

    char c2 ;

};     这个结构体有多大呢?

第一反应大抵是:1+4+1=6吧

但其实:

1.对其规则

1. 第一个成员在与结构体变量偏移量为 0 的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值 。 VS中默认的值为8

3. 结构体 总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍 。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


按照对其规则,这是上述答案的分析:

接下来再看另外一个例子:

eg:

struct S2
{
  char c1;
  char c2;
  int i;
};
int main()
{
  printf("%d", sizeof(struct S2));
  return 0;
}

答案便是:


2.存在原因

结构体的内存对齐是拿空间来换取时间的做法

一些资料也显示说:

  1. 访问效率:内存对齐可以使结构体的成员在内存中连续存储,减少内存访问次数,提高访问效率。如果结构体成员没有进行内存对齐,可能会导致成员之间存在空隙,需要多次访问内存才能获取到所有成员的值
  2. 数据对齐:某些硬件平台要求访问特定类型的数据必须按照特定字节对齐,否则可能会导致访问错误或性能下降。例如,某些处理器要求访问双字节数据(如short类型)必须按2字节对齐,访问四字节数据(如int类型)必须按4字节对齐。如果结构体成员没有进行内存对齐,可能会导致访问错误或性能下降


 

3.减少浪费

那我们如何尽量减少内存对齐所产生的浪费呢?

让占用 空间小的成员尽量集中 在一起

所以上述S1的例子我们可以改造成:

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

结果也确实减小了内存使用:

4.修改默认对齐数

这里我们使用#pragma,可以改变我们的默认对齐数

#pragma pack(8)//设置默认对齐数为8
struct S1
{
  char c1;
  int i;
  char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
  char c1;
  int i;
  char c2;
};
int main()
{
  printf("%d\n", sizeof(struct S1));
  printf("%d", sizeof(struct S2));
  return 0;
}

这次S2的大小便是我们最初认为的6了:


三.位段

1.什么是位段

位段的声明和结构是类似的,有两个不同:

  1. 位段的成员必须是 intunsigned int signed int
  2. 位段的成员名后边有一个冒号和一个数字
struct A {
  int a : 2; //a占用2个bit位
  int b : 5; //b占用5个bit位
  int c : 10;
  int d : 30;
};
int main()
{
  struct A a;
  printf("%d", sizeof(a));
}

不使用位段的话是占16个字节,现在是:

 

2.位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的(一次增加4/1个字节)
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
struct S
{
  char a : 3;
  char b : 4;
  char c : 5;
  char d : 4;
};
int main()
{
  struct S s = { 0 };
  s.a = 10;
  s.b = 12;
  s.c = 3;
  s.d = 4;
  return 0;
}

3.位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大1632位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定

这次关于结构体相关的内容就先到这里啦!                                                                           在下一篇文章中,我们将详细介绍枚举和联合体的内容。感谢大家的支持,加油!!!

 

目录
相关文章
|
2月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
194 14
|
2月前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
77 8
|
2月前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
763 6
|
2月前
|
存储 网络协议 算法
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
93 5
|
2月前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
60 5
|
2月前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
62 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
66 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
60 24

热门文章

最新文章

推荐镜像

更多