数组&字符串&结构体&共用体&枚举体==C语言5

一、程序中内存从哪里来

1、内存管理是由操作系统完成的

  • 内存在物理上是一个硬件,由硬件系统提供。
  • 内存是由操作系统统一管理的。操作系统提供了多种机制来让应用程序使用内存,我们根据实际需要选择合适的机制获取内存、使用内存、释放内存。

2、三种内存来源

  • 栈(stack)、堆(heap)、全局(静态)区(.data 数据段&.bss 段)

3、栈的详解

  • 程序运行时自动分配&自动回收。
  • 反复使用,且使用完之后没有清理。–脏内存
  • 临时性:函数不能返回栈变量的指针,因为这个空间是临时的。
  • 栈会溢出:操作系统事先给定了栈的大小,如果过多地分配栈内存则会导致溢出。

4、堆的详解

  • 程序手动申请&释放:调用 malloc(calloc、realloc)和 free 函数
    • malloc(0):返回一个有效指针,实际分配了一段 16 字节的内存。其实这个答案是不确 定的,因为 C 语言并没有明确规定 malloc(0)时的表现,由各 malloc 库函数 的实现者来定义。
    • malloc(4):gcc 中 malloc 的默认最小分配单位是 16 字节,如果 malloc 申请小于 16 字 节的内存时都会返回 16 字节大小的内存。
    • malloc(20)去访问第 25、250、2500 会怎样?实测:到 2500 出现段错误。
  • 反复使用:使用者释放前不会清除,因此也是脏的。
  • 分配灵活:由堆管理器管理。
  • 大块内存:堆管理器管理着很大的操作系统内存,各进程可按需使用

6、代码段(函数)、.data(初始化的全局+静态)、bss(未初始化全局+静态)

  • 编译器在编译程序的时候,将程序中所有元素分成了几个部分,各部分构成一个段, 所以说段是可执行程序的组成部分
  • 代码段:就是程序中的可执行部分,直观理解代码段就是函数堆叠而成的。
  • .data 数据段:放置初始化了的全局变量和静态变量。
  • bss 段:放置未被显式初始化或被显式初始化为 0 的全局变量和静态变量

7、有些特殊数据会被放到代码段

  • C 语言中使用 char *p = “linux”定义字符串指针时,字符串”linux”实际被分配在代码段, 也就是说它是一个常量字符串。测量字符串
  • const 型常量:const 的实现方法至少有两种,第一种就是编译器将 const 常量放在代码段实现不能被修改(普遍见于各种单片机编译器);第二种就是由编译器来检查以确保 const 常量不会被修改,实际上它是存放在数据段中的(gcc)。

8、总结:c语言中的变量和常量所使用的内存无非以上三种情况,堆、栈、全局(静态)区

  • 相同点:三种获取内存的方法,都可以给程序提供可用内存。
  • 不同点:栈内存对应于 C 语言中的普通局部变量;堆内存完全是独立于程序的存在和管理的;.data 数据段和 bss 段对应于 C 语言中的全局变量和静态局部变量。
  • 函数内部临时使用,就定义局部变量;如果一个变量只在程序的一个阶段有用,就用 堆内存;如果一个变量是和程序一生相伴的,就用全局变量或静态局部变量。
二、C 语言的字符串类型

1、C 语言没有原生字符串类型

  • 很多高级语言像 java、C#等有字符串类型,有个 String 来表示字符串,可以 String s1 = “linux”定义字符串类型变量。
  • C 语言没有字符串类型,C语言中的字符串是通过字符串指针来间接实现的

2、C 语言使用指针来管理字符串

  • C 语言中定义字符串的方法:char *p = “linux”;此时 p 就叫做字符串,其实它是一个字符串指针。

3、字符串的本质:指针指向头部、尾部用’\0’标识的、相连的一段内存。

  • 字符串在内存中就是多个字节连续分布构成的,类似于数组。
  • C 语言中的字符串有 3 个核心要点:
    • ①用一个指针指向字符串头部;
    • ②尾部用’\0’标识;
    • ③各字符在内存中连续存储。
  • ’\0’作为一个特殊的字符被字符串定义结尾标识,产生的副作用就是字符串中无法包 含’\0’这个字符。这种思路叫做“魔数”。

4、注意:指向字符串的指针和字符串本身,是分开的两个东西

  • char *p = “linux”中,指针 p 占 4 个字节,字符串”linux”占 5 个字节,结尾标识’\0’占 1 个字节。总共用了 10 个字节的内存。

5、存储多个字符的两种方式:字符串和字符数组

三、字符串和字符数组细节

1、字符数组初始化与 sizeof、strlen

  • sizeof 是 C 语言的一个关键字,也是一个运算符,接收一个类型或变量,返回它所占用的字节数。
  • strlen 是一个 C 语言库函数,原型是 size_t strlen(const char * str);接收一个字符串指针, 返回这个字符串所占用的字节数,不包含’\0’
  • sizeof(字符数组名)得到的永远是数组元素的个数;strlen(字符数组名)得到字符 串长度,传递合法的字符串进去才有意义。

2、字符串初始化与 sizeof、strlen

  • char *p = “linux”; sizeof§得到的永远是 4;strlen§得到字符串的长度5。

3、字符数组与字符串的本质差异

  • 字符数组 char a[] = “linux”;定义了一个数组,占 6 个字节,分配在栈上。
  • 字符串 char *p = “linux”;定以了一个字符指针 p,占 4 个字节,分配在栈上;同时还定 义了一个字符串”linux”分配在代码段;然后把代码段中的字符串首地址赋值给 p。
四、C 语言之结构体概述

1、结构体类型是一种自定义类型
2、结构体使用是先定义结构体类型,再定义结构体变量
3、结构体中的元素如何访问

  • 数组中元素的访问方式:表面上有两种方式(下标和指针);本质上都是指针方式。
  • 结构体中元素的访问方式:表面上有两种方式(结构体变量用.结构体变量指针用->), 本质上都是指针访问。
五、结构体的对齐访问

1、什么是结构体对齐访问

  • 结构体访问元素本质上是用结构体指针方式,结合该元素在结构体中的偏移量和这个 元素的类型来进行访问。
  • 但实际上偏移量比较复杂,因为结构体要考虑元素的对齐访问,所以每个元素实际占用的字节数和该类型所在占的字节数不一定一样。 比如 char 实际可能占用 1、2、3 或 4 个字节
  • 我们访问元素时,编译器会帮我们做对齐访问,但是做嵌入式开发有必要了解其底层 原理

2、结构体为何要对齐访问

  • 对齐访问是为了配合内存、缓存等硬件,提高访问效率
  • 对齐访问牺牲了内存空间,换取了速度性能。

3、结构体对齐原则

  • 第一个成员的首地址(地址偏移量)为 0。
  • 成员对齐:以 4 字节对齐为例,如果自身类型小于 4 字节,则该成员的首地址是自身 类型大小的整数倍;如果自身类型大于等于 4 字节,则该成员的首地址是 4 的整数倍。 若内嵌结构体,则内嵌结构体的首地址也要对齐,只不过自身类型大小用内嵌结构体 的最大成员类型大小来表示。数组可以拆开看做 n 个数组元素,不用整体看作一个类 型。
  • 最后结构体总体补齐:以 4 字节对齐为例,如果结构体中最大成员类型小于 4 字节, 则大小补齐为结构体中最大成员类型大小的整数倍;如果大于等于 4 字节,则大小补 齐为 4 的整数倍。内嵌结构体也要补齐。
    注意:32 位编译器,一般默认对齐方式是 4 字节。

4、gcc 支持但不推荐的对齐指令

  • #pragma pack():设置编译器 1 字节对齐。
  • #pragma pack(n):设置 n 字节对齐,n = 1/2/4/8/…。

5、gcc推荐的对齐指令

  • attribute((packed)):取消对齐访问。使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有这一类型。
  • attribute((aligned(n))):让整个结构体变量整体进行 n 字节补齐。用法同上。
六、offsetof 宏与 container_of 宏

1、offsetof 宏

  • 作用:用宏来计算结构体中某个元素和结构体首地址的偏移量。
  • 原型:#define offsetof(TYPE.MEMBER) ((size_t)&((YTPE *)0) -> MEMBER)

2、container_of 宏

  • 作用:知道一个结构体中某个元素的指针,反推这个结构体变量的指针。
  • 原型:#define container_of(ptr, type, member) ({const typeof(((type )0) -> member) __mptr = (ptr); (type *)((char *) __mptr – offset(type, member));})
七、共用体

1、共用体类型的定义、变量定义和使用

  • 共用体 union 和结构体 struct 在类型定义、变量定义、使用方法上很相似。
  • 共用体 union 就是对同一块内存中存储的二进制的不同理解方式。
  • 共用体整体要补齐,以 4 字节对齐为例,如果共用体中最大成员类型小于 4 字节,则 以最大成员类型整数倍为基准补齐;如果共用体最大成员类型大于 4 字节,则以 4 字 节的整数倍为基准补齐。
  • sizeof(union)测到的实际大小是占用内存最大的那个成员的大小+补齐的字节。

2、共用体的主要用途

  • 用在需要对同一个内存单元进行多种不同规则解析的情况下。
  • C语言其实是可以没有共用体的,用指针和强制类型转换可以替代共用体完成同样 的功能,但是共用体的方式更简单、更便捷、更好理解。
#include <stdio.h>
#include <string.h>
#pragma pack(4)
union Data{
	int a;
	double b;
	char c[10];
}data;
struct Num{
	int a;
	double c;
	char b[2];
};
int main(){
	struct Num num;
	data.a=100;
	printf("%d\n",data.a);
	data.b=123.245;
	printf("%f\n",data.b);
	strcpy(data.c,"hello!");
	printf("%s\n",data.c);

	printf("%d\n",sizeof(data));
	pritnf("%d\n",sizeof(num));
	return 0;
}
八、大小端模式

1、什么是大小端模式

  • 大小端源自一本小说,最早在计算机通信领域应用,根据先发送高字节还是低字节区 分大小端。
  • 现在我们提到的大小端是指计算机存储系统的大小端,大端模式:一个变量的高字节 存储在低地址;小端模式:一个变量的低字节存储在低地址。

2、如何判断机器的大小端(共用体)

  • 可行方式:用 union、指针+强制类型转换。
  • 不可行方式:位与(&的时候一定是高字节&高字节)、移位(右移永远是将低字节移 除)、强制类型转换(大小端模式没区别,int 向 char 都是低字节赋值给 char)。

3、通信中的大小端

  • 先发高字节叫大端,先发低字节叫小端。

4、大小端判断(联合体+强制指针类型转换)

#include <stdio.h>
//=======================联合体
int check_sys1()
{
	union{
		int i;
		char c;
	}un;
	un.i=1;
	return un.c;
}
//======================强制类型转换
int check_sys2()
{
	int i=1;
	return (*((char*)&i));
}
int main()
{
	int ret=check_sys1();
	if(1==ret)
		printf("小端\n");
	eles
		printf("大端\n");
	system("pause");
	return 0;
}

九、枚举

1、枚举是什么

  • 枚举本质上是一个被命名的整型常量集合,标识符与整型常量一一对应。
    其定义如下:
    enum 枚举名{
    标识符[=整型常量],
    标识符[=整形常量]...
    标识符[=整形常量]
    }枚举变量;
    
  • 如果枚举没有初始化,即省略掉“=整型常量”时,则从第一个标识符开始,依次赋值 0, 1, 2…。枚举中某个成员赋值后,其后的成员按依次加 1 的规则赋值。枚举的最后一 个逗号可省略。
  • 既可以把标识符赋值给枚举变量,也可以把标识符对应的整型常量赋值给枚举变量。
  • 进行枚举变量值的判断时,可以用标识符,也可以用符号对应的整型常量。
  • 枚举标识符既不是字符常量,也不是字符串常量,使用时不加单双引号。
  • 打印枚举变量的值需要用%d,用%s 则无法打印。

2、c语言为何需要枚举

  • 可以对整型常量进行符号化编码,编程时可以不用看数字而直接看标识符,简单易懂。
  • C 语言没有枚举也是可以的,可以用宏定义替代,它们之间差别很小。

3、枚举和宏定义的区别

  • 枚举是将多个有关联的表示符封装在一个枚举体中,是多一的关系。而宏定义是没有关联的,一对一的。
  • 什么情况下用枚举:当我们要定义的常量是一个有限的集合时,适合用枚举。

4、枚举的定义和使用

  • 除常规的类似结构体的定义方式外,还有如下两种定义方式:
enum{
SUN,//SUN=0
MON,//MON=1
TUE,WEN,THU,FRI,SAT
}today,yesterday;

typdef enum{
	SUN,
	MON,
	TUE,WEN,THU,FRI,SAT
}week;
  • 不能有重名的枚举类型,同时也不能有重名的枚举成员
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值