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


目录
相关文章
|
17天前
|
存储 Java 程序员
结构体和类的内存管理方式在不同编程语言中的表现有何异同?
不同编程语言中结构体和类的内存管理方式既有相似之处,又有各自的特点。了解这些异同点有助于开发者在不同的编程语言中更有效地使用结构体和类来进行编程,合理地管理内存,提高程序的性能和可靠性。
24 3
|
19天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
19天前
|
存储 缓存 算法
结构体和类在内存管理方面有哪些具体差异?
【10月更文挑战第30天】结构体和类在内存管理方面的差异决定了它们在不同的应用场景下各有优劣。在实际编程中,需要根据具体的需求和性能要求来合理选择使用结构体还是类。
|
29天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
42 10
|
28天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
38 3
|
29天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
1月前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
30天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
25 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
14天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
29 6