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

相关文章
|
3天前
|
编译器 测试技术 C语言
【C语言】:自定义类型:结构体的使用及其内存对齐
【C语言】:自定义类型:结构体的使用及其内存对齐
18 7
|
1天前
|
网络协议 编译器 Linux
结构体(C语言)
结构体(C语言)
|
2天前
|
编译器 C语言 C++
【海贼王编程冒险 - C语言海上篇】自定义类型:结构体,枚举,联合怎样定义?如何使用?
【海贼王编程冒险 - C语言海上篇】自定义类型:结构体,枚举,联合怎样定义?如何使用?
5 0
|
2天前
|
存储 编译器 Linux
【c语言】详解结构体
【c语言】详解结构体
5 0
|
6天前
|
编译器 C语言
C语言----自定义类型:联合和枚举
C语言----自定义类型:联合和枚举
|
6天前
|
C语言
C语言---自定义类型:结构体(4)
C语言---自定义类型:结构体
|
6天前
|
编译器 Linux C语言
C语言---自定义类型:结构体(2)
C语言---自定义类型:结构体
|
2天前
|
C语言
【C语言基础篇】字符串处理函数(四)strcmp的介绍及模拟实现
【C语言基础篇】字符串处理函数(四)strcmp的介绍及模拟实现
|
1天前
|
C语言
C语言prinf函数
C语言prinf函数
10 4
|
1天前
|
编译器 程序员 Serverless