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


目录
相关文章
|
15天前
|
存储 C语言
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
21 1
|
7天前
|
安全 C语言
【C语言基础】:内存操作函数
【C语言基础】:内存操作函数
|
7天前
|
程序员 C语言 C++
【C语言基础】:动态内存管理(含经典笔试题分析)-2
【C语言基础】:动态内存管理(含经典笔试题分析)
|
7天前
|
程序员 编译器 C语言
【C语言基础】:动态内存管理(含经典笔试题分析)-1
【C语言基础】:动态内存管理(含经典笔试题分析)
TU^
|
7天前
|
程序员 编译器 C语言
C语言之动态内存管理
C语言之动态内存管理
TU^
9 1
|
15天前
|
测试技术 C语言
数据结构学习记录——树习题—Tree Traversals Again(题目描述、输入输出示例、解题思路、解题方法C语言、解析)
数据结构学习记录——树习题—Tree Traversals Again(题目描述、输入输出示例、解题思路、解题方法C语言、解析)
13 1
|
15天前
|
C语言
C语言学习记录——动态内存开辟常见的错误
C语言学习记录——动态内存开辟常见的错误
11 1
|
1天前
|
C语言
C语言动态内存分配
C语言动态内存分配
|
7天前
|
存储 小程序 编译器
【C语言基础】:数据在内存中的存储
【C语言基础】:数据在内存中的存储
|
15天前
|
存储 算法 安全
数据结构学习记录——图应用实例-拯救007(问题描述、解题思路、伪代码解读、C语言算法实现)
数据结构学习记录——图应用实例-拯救007(问题描述、解题思路、伪代码解读、C语言算法实现)
16 0

热门文章

最新文章