【C语言】结构体内存对齐

目录

引入结构体

结构的声明

创建和初始化

内部元素的使用;

特殊声明:

结构体在内存中的对齐

练习:



引入结构体

C语言有各种数据类型,我们已经对一些数据类型很熟悉:

  1. 整型(int)- 存储整数值,包括有符号和无符号两种类型。

  2. 浮点型(float、double、long double)- 存储实数值,包括单精度(float)、双精度(double)和长双精度(long double)三种类型。

  3. 字符型(char)- 存储单个字符,包括有符号和无符号两种类型。

  4. 指针类型(pointer)- 存储内存地址,可以指向其他数据类型。

但是,仅仅有这些类型是无法描述一些对象的特征的,比如:一个人的多个信息,一个家庭的多个成员等等,那么这时候,就要用到结构体类型来描述一个比较复杂的对象。

结构体类型就像其他简单数据类型(int,float,double)一样,也是一种数据类型,但是它比较复杂,可以包含多个 简单的数据类型, 这些被包含的数据类型可以是不同的类型。

++总之,结构体是为了描述复杂对象而引入的一种新的数据类型。++

结构的声明

C语言中,结构体(struct)是一种自定义的数据类型,可以将不同的数据类型组合在一起形成一个 新的数据类型。结构体的定义格式如下:

cpp 复制代码
struct 结构体名称
{
    数据类型1 成员变量1;
    数据类型2 成员变量2;
    ...
} 创建的同类型结构体名称;

有了结构体类型,我们就可以描述复杂对象的特征。例如,要描述一个家庭的成员的信息,可以:

创建结构体类型Person------包含变量字符串型name, 整型age, 双精度浮点型heignt;

创建结构体类型Family------包含结构体类型Person, 字符串型location;

代码如下:

cpp 复制代码
​//声明Person结构体类型

struct Person {
    char name[20];
    int age;
    double height;
};



//声明Family结构体类型

struct Family
{
    struct Person;
    char location[10];
}Family_of_my;

​

从上述代码可以得出:

结构体名称 根据具体的实际意义来决定。

成员变量可以有多个,每个成员变量的数据类型可以是任意数据类型,包括简单数据类型和复杂数据类型。也就是说,一个结构体内部可以再嵌套包含一个结构体类型。

++总结,结构体内部的数据类型多样,可以是简单类型,也可以是复杂类型。++

创建和初始化

结构体的初始化可以按照不同方式进行:

1.按照结构体内部元素顺序

2.按照指定顺序

cpp 复制代码
#include <stdio.h>
struct Stu
{
    char name[20];//名字

    int age;//年龄

    char sex[5];//性别

    char id[20];//学号

};

int main()
{

    //按照结构体成员的顺序初始化

    struct Stu s = { "张三", 20, "男", "20230818001" };

    printf("name: %s\n", s.name);
    printf("age : %d\n", s.age);
    printf("sex : %s\n", s.sex);
    printf("id : %s\n", s.id);

    //按照指定的顺序初始化

    struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥

    printf("name: %s\n", s2.name);
    printf("age : %d\n", s2.age);
    printf("sex : %s\n", s2.sex);
    printf("id : %s\n", s2.id);
    return 0;

}

总结, 初始化可以按照正常顺序,还可按照指定顺序。

内部元素的使用;

要用到结构体操作符。

cpp 复制代码
​
​
#include <stdio.h>
struct Stu
{
    char name[20];//名字

    int age;//年龄

    char sex[5];//性别

    char id[20];//学号

};

int main()
{

    //按照结构体成员的顺序初始化

    struct Stu s = { "张三", 20, "男", "20230818001" };

    printf("name: %s\n", s.name);
    printf("age : %d\n", s.age);
    printf("sex : %s\n", s.sex);
    printf("id : %s\n", s.id);

    //按照指定的顺序初始化

    struct Stu* s2 = &s;

    printf("name: %s\n", s2->name);
    printf("age : %d\n", s2->age);
    printf("sex : %s\n", s2->sex);
    printf("id : %s\n", s2->id);
    return 0;

}

​

​

总结,要使用结构体内部元素,要用到结构体操作符:

. 和 ->

++用例:++

结构体.成员名

结构体指针->成员名

特殊声明:

在声明结构的时候,可以不完全的声明。也就是,可以省略结构体的标签。

例如:

cpp 复制代码
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;

struct
{
int a;
char b;
float c;
}a[20], *p;

但是,这两个匿名的类型完全一致的结构体是同一个结构体类型吗?

也就是说:

cpp 复制代码
p = &x;

这段代码合法吗?

经过测试,编译器会把上面的两个声明当成完全不同的两个类型,所以是⾮法的。

匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。

结构体在内存中的对齐

现在,我们已经对结构体有了一个基本的认识,那么,结构体的大小是如何计算的呢?


我们先看一个实例:

计算下列两个结构体的大小:

cpp 复制代码
//练习1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));


//练习2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));

我们可以先猜测:

既然两个结构体内部的变量类型和数目完全一致,那么我们有理由猜测两个结构体大小相同,但是,实际情况并不是这样:

其实,结构体在内存中的存储有一套自己的规则:

首先,引入一个概念:

对齐数

对齐数=编译器默认的⼀个对齐数 和 该成员变量长度的较小值

默认对齐数 :

VS 中默认的值为 8

Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩

结构体的基本对齐方法是:

1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到对齐数的整数倍的地址处

结构体大小的计算:

1.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。

2.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

这样一来,我们分析上两个问题:


对于s1,先放入char类型,由于char类型是1个字节,小于8(VS默认值),所以直接放在0地址处。

值得一提,长度是1的char无论何时都不会浪费空间,它会直接顶着上一个数据的存储空间放置。

int型,大小4字节,小于8(VS默认值),所以放在sizeof(int)的整数倍的地址处,也就是4开始,到7结束。

char 紧跟着int型;


由于最大对齐数是4,此时结构体的最小大小大于8,要满足4的整数倍,那么这个结构体的大小向上取 是12。

对于s2 :

放入两个char,一个int后的结构体最小大小正好是8,那么结构体总大小就是8。


练习:

cpp 复制代码
int main(int argc, char* argv[])
{
  struct tagTest1
  {
    short a;
    char d; 
    long b;   
    long c;   
  };
  struct tagTest2
  {
    long b;   
    short c;
    char d;
    long a;   
  };
  struct tagTest3
  {
    short c;
    long b;
    char d;   
    long a;   
  };
  struct tagTest1 stT1;
  struct tagTest2 stT2;
  struct tagTest3 stT3;

  printf("%d %d %d", sizeof(stT1), sizeof(stT2), sizeof(stT3));
  return 0;

结果:


完~

未经作者同意禁止转载

相关推荐
爱吃生蚝的于勒2 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
ChoSeitaku7 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
DdddJMs__1357 小时前
C语言 | Leetcode C语言题解之第557题反转字符串中的单词III
c语言·leetcode·题解
娃娃丢没有坏心思7 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
ahadee9 小时前
蓝桥杯每日真题 - 第11天
c语言·vscode·算法·蓝桥杯
No0d1es10 小时前
2024年9月青少年软件编程(C语言/C++)等级考试试卷(九级)
c语言·数据结构·c++·算法·青少年编程·电子学会
Che3rry10 小时前
C/C++|关于“子线程在堆中创建了资源但在资源未释放的情况下异常退出或挂掉”如何避免?
c语言·c++
kuiini12 小时前
C 语言学习-02【编程习惯】
c语言·学习
木辛木辛子12 小时前
L2-2 十二进制字符串转换成十进制整数
c语言·开发语言·数据结构·c++·算法