C语言---自定义类型:结构体(3)

简介: C语言---自定义类型:结构体

C语言---自定义类型:结构体(2)https://developer.aliyun.com/article/1544451

那么为什么会存在内存对齐的问题呢

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

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定

类型的数据,否则抛出硬件异常。

  1. 性能原因:

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

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

内存对齐可以提升读取的效率

所以内存对齐还是很有必要的

那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到?

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

//那我们只能眼睁睁的看着字节被浪费吗?
/*
我们是否有办法使内存少浪费一些
 
那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到?
 
让占用空间小的成员尽量集中在一起
 
 
*/
/*这个时候我们就可以拿出之前的S1和S2了
struct S1
 {
    char c1;
    int i;
    char c2;
 };
 
 struct S2
 {
    char c1;//对齐数是1
    char c2;//对齐数是1
    int i;
 };
 这两个类型成员是一样的,只是内部成员顺序不同
 S1占12个字节
 S2占8个字节
 
 不难发现,我们将占用空间较小的成员集中放在一起,
 原本要浪费的空间就被利用起来了
 
 
*/

修改默认对齐数

pragma这个预处理指令,可以改变编译器的默认对齐数

//vs上默认对齐数是8
 
#pragma pack(1)//将默认对齐数设置为1
struct S
{
    char c1;
    int n;
    char c2;
 
};
#pragma pack()//恢复默认对齐数,恢复默认对齐数为8
//如果想恢复默认对齐数,我们pack()括号内可以直接不写
int main()
{
    //printf("%zd\n", sizeof(struct S));//12个字节---默认对齐数为8的时候
    printf("%zd\n", sizeof(struct S));//6个字节-=--默认对齐数是1的时候
    return 0;
}

3.结构体传参

//结构体传参
struct S//我们必须将这个结构体类型的创建放在前面,不然会报错的
{
    int arr[1000];
    int n;
    char ch;
};
 
void print1(struct S tmp)//传过来的是一个结构体变量,那么我们就创建一个结构体变量进行接收
{
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", tmp.arr[i]);
    }
    printf("\n");
    printf("n=%d\n", tmp.n);
    printf("ch=%c\n", tmp.ch);
 
}
 
void print2(const struct S* ps)//传过来的是结构体变量的地址,那么我们就创建一个结构体指针进行接收
{//那么ps就指向了s
 
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", ps->arr[i]);
    }
    printf("\n");
    printf("n=%d\n", ps->n);
    printf("ch=%c\n",ps->ch );
 
}
int main()
{
    struct S s = { {1,2,3,4,5,6,7,8,9,10},10,'w' };//创建结构体变量s并进行初始化
    //利用函数将结构体内数据打印出来
    print1(s);//直接将结构体变量s传过去-----传值调用
    print2(&s);//直接将结构体变量s的地址传过去
    return 0;
}
 
 
//分析:那种更好一些
/*
在print1中,我们传过去的是结构体变量,
相当与传值调用
在调用的时候,我们还要创建一个结构体变量tmp用来接收传过来的数据
在这个调用的时候,我们还要额外的空间进行结构体变量的创建
 
假如传过来的变量占用空间很大,我们又要创建一个一模一样的空间进行存储
 
我认为这么很浪费空间,还很消耗时间
 
*/
 
/*
但是我们利用print2传的仅仅只是地址,地址的大小是4个字节或者8个字节
 
我们在创建形参的时候仅仅只需要创建一个指针ps进行接收,
指针ps就能找到这个结构体变量内的数据
这样也不需要创建结构体指针变量进行数据的拷贝,我们直接利用地址进行数据的调用和访问
*/
 
//传值能做到的,传地址一定能做到
//传地址能做到的,传值不一定能做到
 
//所以我们在结构体传参的时候,要传结构体的地址
 
//如果不想因为地址传过去的原因,地址指向的数据被改变了,我们直接加上const

在我们进行结构体传参的时候,我们传地址就行了

4.结构体实现位段

结构体讲完就得讲讲结构体实现位段的能力

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


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

2.位段的成员名后边有一个冒号和一个数字


位段中的位指的是二进制中的位

//二进制中一位就是一个比特位
//一个字节是8个比特位
struct S
{
    /*int _a:2;*///_a占2个比特位
    /*
    假设我们在设定a的值的时候,我们只想要a是0 1 2 3 这4个数字
    那么二进制位表达出来就是
    0---00
    1---01
    2---10
    3---11
 
    这四个数刚好满足两个比特位的要求
    如果我们给a32个比特位的话就有点浪费空间了
    */
 
 
 
    int _a : 2;
    int _b:5;//_b占5个比特位
    int _c:10;//_c占10个比特位
    int _d : 30;//_d占30个比特位
    //int _e : 50;//这种写法是错的,对于一个整型来说,最大也就32个比特位,没有50
};
 
int main()
{
    printf("%zd\n", sizeof(struct S));//8
    /*
    如果不进行位段的调整,将是16个字节
 
    */
    return 0;
}
//这种设计相当于限制了大小,可以减小空间,避免空间浪费
//8个比特位等于1个字节

通过位段的使用,可以减少空间的占有,减少空间浪费

限制数据的长度、大小

节省空间

C语言---自定义类型:结构体(4)https://developer.aliyun.com/article/1544455

相关文章
|
27天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
123 14
|
1月前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
147 10
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
153 13
|
2月前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
2月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
69 11
|
2月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
60 4
|
3月前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
91 10
|
3月前
|
安全 编译器 Linux
【c语言】轻松拿捏自定义类型
本文介绍了C语言中的三种自定义类型:结构体、联合体和枚举类型。结构体可以包含多个不同类型的成员,支持自引用和内存对齐。联合体的所有成员共享同一块内存,适用于判断机器的大小端。枚举类型用于列举固定值,增加代码的可读性和安全性。文中详细讲解了每种类型的声明、特点和使用方法,并提供了示例代码。
31 3
|
3月前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
3月前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。