C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)二

简介: C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)二

C语言学习记录——结构体(声明、初始化、自引用、内存对齐、结构体设计、修改默认对齐数、结构体传参)一:https://developer.aliyun.com/article/1530419


结构体内存对齐

深入讨论一个问题:计算结构体的大小

struct S1
{
    char c; //1字节
    int i;  //4字节
    char c2;//1字节
};
int main()
{
    struct S1 s1 = { 0 };
    printf("%d\n", sizeof(s1));
    return 0;
}

结果显示为12,这涉及到了结构体内存对齐的知识点。我们先了解结构体内存对齐的规则

对齐规则

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

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

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

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。要更好地理解这个对齐规则,我们要先简单了解一些概念。

偏移量

对齐数

对齐数= 编译器默认的一个对齐数与该成员大小的 较小值

VS中默认的对齐数的值为8,;而Linux没有默认对齐数的概念。

这个时候我们拿起刚刚那道题,用对齐规则来解决。

练习

struct S2
{
    char c;
    int i;
    double d;
};
struct S3
{
    double d;
    char c;
    int i;
};
int main()
{
    struct S2 s2 = {0};
    struct S3 s3 = {0};
}

试着算算s2和s3的大小,答案在后文公布。



嵌套结构体大小计算

struct S4
{
    char c1;
    struct S3 s3;
    double d;
};
int main()
{
    struct S4 s4 = { 0 };
    printf("s4 = %d\n", sizeof(s4));
    return 0;
}

练习答案


嵌套结构体计算大小:

存在内存对齐的原因

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

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

2.性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次访问;而对齐的内存访问仅需要一次访问。

对“访问未对齐的内存,处理器需要作两次访问;而对齐的内存访问仅需要一次访问。”做解释:


总的来说


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


结构体的设计

在设计结构体的时候,我们既要满足对齐,又要节省空间。

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

例如:

struct S1
{
    char c1;
    int n;
    char c2;
}s1;
struct S2
{
    char c1;
    char c2;
    int n;
}s2;
int main()
{
    printf("s1 = %u\n", sizeof(s1));
    printf("s2 = %u\n", sizeof(s2));
    return 0;
}

修改默认对齐数

使用#pragma这个预处理指令,可以改变默认对齐数。

具体为:

#include <stdio.h>
#pragma pack(2)  //修改默认对齐数为2
struct S1
{
    char c1;
    int n;
    char c2;
}s1;
#pragma pack()  //取消设置的默认对齐数,还原为8
struct S2
{
    char c1;
    int n;
    char c2;
}s2;
int main()
{
    printf("s1 = %u\n", sizeof(s1));//默认对齐数为2时的大小
    printf("s1 = %u\n", sizeof(s1));//默认对齐数为8时的大小
    return 0;
}

变量

占用大小

默认对齐数

对齐数

c1

1

8

1

n

4

8

4

c2

1

8

1

变量

占用大小

默认对齐数

对齐数

c1

1

2

1

n

4

2

2

c2

1

2

1

结论

结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。

结构体传参

试着对比下面两种传参方式

#include <stdio.h>
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;
}

我们应该首选print2函数。

原因:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

而且,直接传结构体会有一些限制。例如,并不能改变结构体内部成员的值,而传地址既节省系统开销,提高性能,又不会有太多限制。

#include <stdio.h>
struct S
{
    int data[1000];
    int num;
};
struct S s1 = { {1,2,3,4},1000 };
struct S s2 = { {1,2,3,4},1000 };
 
//结构体传参 修改成员变量的值
void change1(struct S s1)
{
    s1.num = 2000;
}
 
//结构体地址传参 修改成员变量的值
void change2(struct S* ps)
{
    ps->num = 2000;
}
int main()
{
    change1(s1);  
    change2(&s2);
    printf("s1.num = %d\n", s1.num);
    printf("s2.num = %d\n", s2.num);
    return 0;
}



而当你使用结构体地址传参且不需要改变其成员变量的值时,可以加上const修饰。


目录
相关文章
|
5月前
|
存储 人工智能 搜索推荐
一种专为AI代理设计的内存层,能够在交互过程中记忆、学习和进化
Mem0 是专为 AI 代理设计的内存层,支持记忆、学习与进化。提供多种记忆类型,可快速集成,适用于开源与托管场景,助力 AI 代理高效交互与成长。
671 123
一种专为AI代理设计的内存层,能够在交互过程中记忆、学习和进化
|
7月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
380 15
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
617 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
1488 13
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
496 12
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
509 6
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
511 11
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
404 6
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
存储 算法 C语言
C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项
本文深入探讨了C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项,并通过案例分析展示了实际应用,旨在帮助读者提高编程效率和代码质量。
801 4