C 结构体基础知识&(内存对齐详解)& 常见误用

    xiaoxiao2023-10-08  165

    目录

    结构体诞生 构造新类型!

    结构体构造类型 typedef 

    (1)typedef 使用

    (2)typedef 与 define 

    结构体初始化&赋值

    1、初始化

    手动 scanf 赋值

    结构体类型作参数和返回值

    1、结构体变量 作参数和返回值

    2、结构体指针作参数 ——提升效率

    .结构体数组

    结构体 sizeof 大小 以及内存对齐

    (1)什么是内存对齐 (见下图)

    (2)对齐规则

    结构体使用注意事项


    结构体诞生 构造新类型!

    道家思想:“道生一,一生二,二生三,三生万物”

    也就是 : 从单变量->数组->结构体 

    1:1 一个变量,一个类型   单一类型变量

    N:1 N个变量,一个类型   数组

    N:M N个变量,M个类型   结构体 

    为了更方便的描述事情,C语言对变量类型 开放权限,允许在基础类型的基础上进行自由构造。

    typedef struct stu { int num; char name[20]; char sex; float score; }Stu; Stu s1; //定义 与 int a; 地位等价

    结构体构造类型 typedef 

    (1)typedef 使用

          1、先用原类型定义变量

          2、定义之前加 typedef 

         3、将原变量的名字 换成你需要的类型名

    注意:typedef 只是对现有类型 取别名,不能创造新类型。

    (2)typedef 与 define 

    Define  构成的语句在预处理阶段——进行文本替换,不过脑子的那种替换

    typedef  构成C语言的语句;参与编译

    #define pointer char* pointer m,n; printf("\nsizeof(m) = %d\t sizeof(n) = %d\n",sizeof(m),sizeof(n)); typedef pointer char* pointer m,n; printf("\nsizeof(m) = %d\t sizeof(n) = %d\n",sizeof(m),sizeof(n));

     

    结构体初始化&赋值

    1、初始化

        凡是基本类型,既可以在定义的时候初始化,也可以先定义再赋值。 int a = 100; int a; a = 100;    凡是构造类型,可以在定义的时候初始化,也可以先定义再对每个成员进行赋值~ 绝不可以先定义再以初始化的方式赋值 //数组 int arr[10] = {1,2,3,4}; //定义的同时初始化 int arr[10]; arr[0] = 1; arr[2] = 2; //先定义 再对每个成员赋值 int arr[10]; arr[10] = {1,2,3,4}; //先定义再以初始化的形式赋值 不可以哦 //结构体也是如此 struct stu s = {"jiaomingxin",07,'f',100}; //定义+初始化 s.num = 10; //每个成员赋值 s.score = 100; strcpy(s.name,"jiao"); //对于字符数组的赋值可以使用strcpy拷进去

    手动 scanf 赋值

       scanf("%s%d %c%f",s.name,&s.num,&s.sex,&s.score); 

    (1)和 %c 打交道的时候 容易出错

    在输入num %d的数值之后,空格或者换行都会占据 %c 的位置!使 %c 成功被忽略。在连续输入时,%c前边加一个空格解决此类问题。

    (2)取地址符 &  

    结构体定义时,name为数组类型,num、sex、score都是基本类型。 取地址时,数组不用 &符号,其他得用。

     

    结构体类型作参数和返回值

    分两种:一种为结构体变量整个作参数并返回;一种是以结构体指针形式作参并返回。

    1、结构体变量 作参数和返回值

    结构体作参数或返回值,会发生同类型复制(本质是赋值)。同类型结构体间,是可以相互赋值的。 BUT 当结构体内的变量数目较大的时候,会进行大量的内存拷贝。可能会导致栈的进程空间不足而崩溃。

    #include <stdio.h> typedef struct _Mycomplex { float real; float imag; }Mycomplex; Mycomplex ADDcomplex (Mycomplex pa,Mycomplex pb) { Mycomplex t; t.real = pa.real + pb.real; t.imag = pa.imag + pb.imag; return t; } int main() { //结构体变量 作参数 Mycomplex s1,s2,res; s1.real = 1; s1.imag = 2; s2.real = 3; s2.imag = 4; res = ADDcomplex(s1,s2); printf("%.2f+%.2f",res.real,res.imag); return 0; }

     

    2、结构体指针作参数 ——提升效率

      传结构体的成本是很高的,用指针作参数有一个好处,就是避免了同类型复制,无论结构体多大,只传 4 个字节的指针。 (注意:结构体变量的 点成员运算符 和 地址的 指向成员运算符 的使用)

    #include <stdio.h> typedef struct _Mycomplex { float real; float imag; }Mycomplex; Mycomplex ADDcomplex (Mycomplex *pa,Mycomplex *pb) { Mycomplex t; t.real = pa->real + pb->real; t.imag = pa->imag + pb->imag; return t; } int main() { //传地址 Mycomplex s1 = {1,2},s2 = {3,4},res; //s1.real = 6; //s1.imag = 2; //s2.real = 3; //s2.imag = 4; //要么在结构体初始化时赋值,要么对结构体成员单独赋值。不存在 s1 = {1,2}; res = ADDcomplex(&s1,&s2); printf("%.2f+%.2f",res.real,res.imag); return 0; } //传地址与返回地址 /* Mycomplex * ADDcomplex (Mycomplex *pa,Mycomplex *pb) { Mycomplex *t; t->real = pa->real + pb->real; t->imag = pa->imag + pb->imag; return t; } int main() { //传地址 Mycomplex s1 = {1,2},s2 = {3,4}; Mycomplex * res; res = ADDcomplex(&s1,&s2); printf("%.2f+%.2f",res->real,res->imag); return 0; } */

     

    .结构体数组

    结构体数组的本质还是一维数组,只不过一维数组的每一个成员又是结构体

    #include <stdio.h> typedef struct stu { int num; char name[20]; char sex; float score; }Stu; int main() { //结构体数组 Stu s[] ={{101,"jiaomingxin",'m',100},{102,"jackma",'m',99},{103,"mars",'f',20}}; int n = sizeof(s)/sizeof(*s); for(int i=0; i<n; i++) { printf("s[%d]num --> %d\n",i,s[i].num); printf("s[%d]name --> %s\n",i,s[i].name); printf("s[%d]sex --> %c\n",i,s[i].sex); printf("s[%d]score --> %.1f\n",i,s[i].score); puts(""); } return 0; }

    结构体 sizeof 大小 以及内存对齐

    (1)什么是内存对齐 (见下图)

    首成员在低地址,尾成员在高地址

    (2)对齐规则

    x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。 方法: ①取 pack(n)的值(n= 1 2 4 8--),取结构体中类型最大值 m。两者取小即为外对齐大 小 Y= (m<n?m:n)。 ②将每一个结构体的成员大小与 Y 比较取小者为 X,作为内对齐大小. ③所谓按 X 对齐,即为地址(设起始地址为 0)能被 X 整除的地方开始存放数据。 ④外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。

    #include <stdio.h> #include <string.h> #pragma pack(8) typedef struct _staff { char sex; int age; short num; }Staff; #if 0 x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。 具体操作步骤: ①取 pack(n)的值(n= 1 2 4 8--),取结构体中类型最大值 m。两者取小即为外对齐大 小 Y= (m<n?m:n)。 ②将每一个结构体的成员大小与 Y 比较取小者为 X,作为内对齐大小. ③所谓按 X 对齐,即为地址(设起始地址为 0)能被 X 整除的地方开始存放数据。 ④外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。 以此为例: #pragma pack(8) typedef struct _staff { char sex; int age; short num; }Staff; 1、pack n = ? 结构体中类型最大的 m Y=min(m,n) 外对齐 n=8;m=4 --> Y=4 2、每个成员 char sex; 1 与Y相比取小值为X X = 1 int age; 4 4 short num; 2 2 存储位置 3、按X对齐 sex 0 - 1 (1字节) 往后找能被1整除的 那就1了 (设起始地址为 0) age 1 - 5 (4字节) 往后招能被4整除的 到了 8 num 8 - 10(2字节) OK存完了 4、外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。 Y=4 4的最小整倍数,10不是,找到12 分析完毕! #endif int main() { Staff s; printf("sizeof(Staff) = %d\n",sizeof (Staff)); printf("sizeof(s) = %d\n\n",sizeof (s)); printf("&(s) = %p\n",&s); printf("&(sex) = %p\n",&s.sex); printf("&(age) = %p\n",&s.age); printf("&(num) = %p\n",&s.num); return 0; }

    结构体使用注意事项

    1、向未分配空间的结构体指针拷贝!

     应用指针之前,一定要确保指针已有空间!否则没有指向合法的地址,考进去就是一堆乱码,并且后续也无法正常访问空间。

    #include <stdio.h> #include<string.h> #include <stdlib.h> typedef struct stu { char *name; }Stu; int main() { Stu s; // strcpy(s.name,"mars"); // printf("s. name = %s\n",s.name); //错误1 向未分配空间的指针赋值 s.name = (char *)malloc(20); strcpy(s.name,"mars"); printf("s. name = %s\n",s.name); Stu *p; p = (Stu *)malloc(sizeof(Stu *)); //为整个结构体指针分配空间 p->name = (char *)malloc(20); //还得为name指针分配空间 strcpy(p->name,"marsss"); printf("s. name = %s",p->name); free(p->name); //先释放内层空间 free(p); //后释放外层的 return 0; }

    注意:

    1)结构体中,包含指针,注意指针的赋值,切不可向未知区域拷贝

    2)name 指针未初始化时,并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用 strcpy 函数时,会将字符串 "mars" 往乱码所指的内存上拷贝,内存 name 指针根本就无权访问,导致出错

        在所有涉及指针的时候,要么先定义的时候初始化,要么在使用之前进行malloc分配堆空间。

    3)多层指针每个都要初始化。为指针变量 (Stu) p 分配了内存,但是同样没有给 name 指针分配内存。(p只是申请了name指针的指针类型的4个字节,与2)情况一样崩溃)错误与上面上一种情况一样,解决的办法也一样。这里用了一个 malloc 给人一种错觉,以为也给name 指针分配了内存。

    4)记得:完了要释放结构体内指针所指向的空间,由内而外逐个释放。-->内存泄漏(先释放p,p.name就找不到了 也就无法释放,造成内存泄漏)

     

     

    最新回复(0)