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

目录

结构体诞生 构造新类型!

结构体构造类型 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就找不到了 也就无法释放,造成内存泄漏)

 

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值