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修饰。


目录
相关文章
|
19天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
95 14
|
23天前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
101 10
|
27天前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
116 13
|
1月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
60 11
|
1月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
266 1
|
20天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
29天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
1月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
21 3
|
1月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
48 1