我是靠谱客的博主 端庄背包,最近开发中收集的这篇文章主要介绍C语言结构体知识点梳理1 结构体基本概念2 结构体对齐3 多文件编译情况P.S.:,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

C语言结构体知识点梳理

  • 1 结构体基本概念
    • 1.1 定义与创建
    • 1.2 初始化与调用
  • 2 结构体对齐
    • 2.1 默认内存对齐
    • 2.2 pragma强制对齐
    • 2.3 变量位域设置
  • 3 多文件编译情况
    • 如何修改结构体
  • P.S.:
    • 结构体变量存储详情

1 结构体基本概念

1.1 定义与创建

一、通常定义

// 定义
struct Person{
    int number;
    char sex;
    double money;
}p1;

// 创建 & 初始化:需要分配变量名
p1 = {}; //全局变量(高址->低址)
struct Person p2 = {}; //局部变量 (高址->低址)
static struct Person p3 = {}; //静态全局变量 (低址->高址)
p1.number = 1; // 正确的初始化方式
p2.number = 1; // 正确的初始化方式
p3.number = 1; // 正确的初始化方式

二、匿名定义

struct{
	int number;
	char sex;
	double money;
}P;

// 创建 & 初始化:无法创建除 P 外其他的结构体变量
// P = {}; // 错误的初始化方式
P.number = 1; // 正确的初始化方式(唯一全局变量)

三、类型定义

typedef struct{
    int number;
    char sex;
    double money;
}W;

// W 等价替代 struct Person
W w00 = {}; //此时的 w00 仅为局部变量
static W w01 = {}; //此时的 w00 为静态全局变量
w00.number = 1;

四、多重定义

typedef struct People{
    int number;
    char sex;
    double money;
}w1, w2;

// w1 等价替代 struct People
// 定义静态变量的 2 种方法
static struct People p000 = {};
static w1 w111 = {};
// 定义局部变量的 2 种方法
struct People p = {};
w1 w112 = {};

有关变量地址的讨论详见p.s.

1.2 初始化与调用

一、顺序初始化
二、指定初始化

调用原则:
给(返回值(将作为右值),参数列表实际赋值):地址
取(左值,参数列表定义):指针
子函数内:不加&或*

演示代码

#include <stdio.h>

struct student_st
{
    char c;
    int score;
    const char *name;
};

static void show_student(struct student_st *stu)
{
    printf("c = %c, score = %d, name = %sn", stu->c, stu->score, stu->name);
}

int main(void)
{
    // method 1: 按照成员声明的顺序初始化
    struct student_st s1 = {'A', 91, "Alan"};
    show_student(&s1);

    // method 2: 指定初始化,成员顺序可以不定,Linux 内核多采用此方式
    struct student_st s2 =
    {
        .name = "YunYun",
        .c = 'B',
        .score = 92,
    };
    show_student(&s2);

    // method 3: 指定初始化,成员顺序可以不定
    struct student_st s3 =
    {
        c: 'C',
        score: 93,
        name: "Wood",
    };
    show_student(&s3);

    return 0;
}

2 结构体对齐

2.1 默认内存对齐

规则:
1、结构体内存容量是最大类型变量的整数倍
2、默认留空对齐。
示例1:【常规对齐】

struct Demo1{
	char arr[10];//类型长度为1,长度共10
	int num;//类型长度为4
	char str[3];//类型长度为1,长度为3
	double node;//类型长度为8,结构体存储基准
	long n;//类型长度为4
	char ch;//类型长度为1
};
// 内存图:
/*高地址↑
[lon0][lon1][lon2][lon3][ ch ][    ][    ][    ]
[dou0][dou1][dou2][dou3][dou4][dou5][dou6][dou7]
[str0][str1][str2][    ][    ][    ][    ][    ]
[arr8][arr9][    ][    ][num0][num1][num2][num3]
[arr0][arr1][arr2][arr3][arr4][arr5][arr6][arr7]
低→高
低地址↑*/
// 内存占用比:30/40Byte
// 寻址有效率:6/10

示例2:【极限对齐】

struct demo{
    int num;
    char ch;
    char arr[3];
    double dot;
}d;
// 内存图:
/*高地址↑
[dou0][dou1][dou2][dou3][dou4][dou5][dou6][dou7]
[num0][num1][num2][num3][ ch ][arr0][arr1][arr2]
低→高
低地址↑*/

2.2 pragma强制对齐

规则:
#pragma pack(N) 可将结构体以规定长度N(1、2、4、8)为存储基准。
此时将不再以
变量类型最长的长度
为默认存储基准。
示例:

#pragma pack(4)
struct Demo1{
	char arr[10];//类型长度为1,长度共10
	int num;//类型长度为4
	char str[3];//类型长度为1,长度为3
	double node;//类型长度为8,结构体存储基准
	long n;//类型长度为4
	char ch;//类型长度为1
};
// 内存图:
/*高地址↑
[ ch ][    ][    ][    ]
[lon0][lon1][lon2][lon3]
[dou4][dou5][dou6][dou7]
[dou0][dou1][dou2][dou3]
[str0][str1][str2][    ]
[num0][num1][num2][num3]
[arr8][arr9][    ][    ]
[arr4][arr5][arr6][arr7]
[arr0][arr1][arr2][arr3]
低→高
低地址↑*/
// 内存占用比:30/36Byte
// 寻址有效率:6/18
/*|* OTHERS *|*/
/** pack(2) 
// 内存占用比:30/32Byte
// 寻址有效率:6/32
*/
/** pack(1) 
// 内存占用比:30/30Byte
// 寻址有效率:6/30
*/

2.3 变量位域设置

规则:
将结构体中变量按bit位取内存
位域限定的3种变量类型:int,unsigned int,char

示例1:【默认】

struct Owner{
    char name;
    short age;
};
struct demo{
	char addr[2];
	char apart;
	char floor;
	struct Owner p;
};
// 内存图:
/*高地址↑
[age0][age1]
[name][    ]
[apar][floo]
[add0][add1]
低→高
低地址↑*/

示例2:【位域】

struct Owner{
    char name:3;
    short age;
};
struct demo{
	char addr[2];
	char apart:1;
	char floor:4;
	struct Owner p;
};
// 内存图:
/*高地址↑
[age0][age1]
[0:0:0:0:0:n2:n1:n0][    ]
[0:0:0:f3:f2:f1:f0:a0][    ]
[add0][add1]
低→高
低地址↑*/

p.s.:虽然apart + floor + p.name恰好可以凑成一字节,但是并未跨结构体拼接在一起(即使加上#pragma pack(1) 也是如此)。
注意:
位域是不跨平台的,可移植的程序应避免使用位域。

1、int位域是有符号还是无符号是不确定的;
2、位域最大数目不确定;
3、位域成员在内存中从左向右分配,还是从右向左分配尚未定义;
4、当一个结构包含两个位域,第二个位域成员较大,无法容纳第一个位域剩余位时,是舍弃还是利用,也是不确定的。
以上链接自:C语言-----结构体知识点总结.

总结:位域可以很好地节省空间,但是存在跨平台问题。

以下是关于位域的拓展案例:
int main()
{
    char pub[4] = { 0 };
    struct S
    {
        char a : 3;
        char b : 4;
        char c : 5;
        char d : 4;
    }*ps;
    // 结构体指针数组
    ps = (struct S*)pub;
    ps->a= 10;
    ps->b = 12;
    ps->c = 3;
    ps->d = 4;
    // 仅看前两个字节
    printf("%02x,%02x,%02x,%02x", pub[0],pub[1],pub[2],pub[3]);
    system("pause");
    return 0;
}

// 输出结果: 62,03,04,00

图解:
在这里插入图片描述

3 多文件编译情况

如何修改结构体

将结构体定义在头文件
在对应c文件中调用结构体变量

封装保护:
头文件中声明对应c文件中内容
将对应c文件封装成静态库

后果:
此时如果在头文件结构体中添加新的属性,
则在对应c文件中调用的结构体变量将不支持

p.s.:
#静态库不会改变,若外界结构变化,静态库要重新进行封装
#静态库编译封装:

// 编译student.c,并封装成静态库
gcc -c student.c 
ar -r libStudent.a student.o
//编译test.c 链接libStudent.a 并运行查看打印的结果
//"student.h" 需要和test.c 位于同一目录下,保证链接成功 
gcc test.c -L. -lStudent -o out
./out           

兼容性强地修改结构体的2种方案
1、空间预留
在对应c文件尚未封装成静态库时:
在头文件的结构体末尾添加预留空间:
int resv[32];
然后再将对应c文件封装成静态库
当需要扩展结构体属性时:
在结构体末尾创建新属性并对应减少预留空间
char new;
int resv[31];
一定要从末尾添加,防止偏移地址紊乱

2、指针扩展
不想对外暴露的对象,可以用指针处理

P.S.:

结构体变量存储详情

演示代码

#include <stdio.h>
// P为全局变量
struct Person{
    int number;
    char sex;
    double money;
}P1,P2;

/** 匿名结构体,无法内部内嵌*/
struct{
    int number;
    char sex;
    double money;
}P;

/** 定义结构体类型 */
// 此时的W为类型
// 结构体不能重名
typedef struct People{
    int number;
    char sex;
    double money;
}w1, w2;

typedef struct{
    int number;
    char sex;
    double money;
}W;

// 结构体定义在函数内部, 无法在外部获得其定义
// extern struct Class;
void print_message();
// 结构体定义必须在使用之前定义,预先声明或扩展声明均无效
// struct Class;
// extern struct Class;
// 只能在外部重新再次定义

int main(int argc, const char *argv[]){
    /**1*/
    static struct Person pxxx = {};
    struct Person pxx = {};

    /**2*/
    sizeof(P);
    P.sex = 'f';

    /**3*/
    W w000 = {};
    static W w001;

    /**4*/
    static struct People p000 = {};
    static w1 w111 = {};
    struct People p = {};
    w1 w112 = {};

    printf("1n");
    printf("full : %xn", &P1);
    printf("static : %xn", &pxxx);
    printf("origin : %xn", &pxx);
    printf("2n");
    printf("full : %xn", &P);
    printf("3n");
    printf("origin : %xn", &w000);
    printf("static : %xn", &w001);
    printf("4n");
    printf("static1 : %xn", &p000);
    printf("static2 : %xn", &w111);
    printf("origin1 : %xn", &p);
    printf("origin2 : %xn", &w112);

    // C不再是全局变量,而是局部变量
    struct Class{
        int number;
        char sex;
        double money;
    }C;

    struct Class c101;
    struct Class c102;
    static struct Class c103;
    printf("5n");
    printf(""full" : %xn", &C);
    printf("origin1 : %xn", &c101);
    printf("origin2 : %xn", &c102);
    printf("static : %xn", &c103);

    print_message();

    return 0;
}

void print_message(){
    // 结构体定义在函数内部,作用域仅在函数内部
    // ip仅为局部变量
    struct Class{
        int number;
        char sex;
        double money;
    }ip;
    struct Class o1;
    struct Class o2;
    // 若把子函数块提前,该动态变量最优先生成
    static struct Class o;
    printf("6n");
    printf("static : %xn", &o);
    printf("local : %xn", &o1);
    printf("local : %xn", &o2);
    printf(""full" : %xn", &ip);
    return;
}

输出详情:

1
full : 4050c0
static : 405008
origin : 60fef0
2
full : 4050b0
3
origin : 60fee0
static : 405018
4
static1 : 405028
static2 : 405038
origin1 : 60fed0
origin2 : 60fec0
5
"full" : 60feb0
origin1 : 60fea0
origin2 : 60fe90
static : 405048
6
static : 405058
local : 60fe50
local : 60fe40
"full" : 60fe60

最后

以上就是端庄背包为你收集整理的C语言结构体知识点梳理1 结构体基本概念2 结构体对齐3 多文件编译情况P.S.:的全部内容,希望文章能够帮你解决C语言结构体知识点梳理1 结构体基本概念2 结构体对齐3 多文件编译情况P.S.:所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部