目录
1. 内存管理方式
在C语言程序中,存放数据所能使用的内存空间大概分为四种情况:栈stack、堆heap、数据区【也被成为静态数据区,静态区】(.data和.bss区,限定为数据,所以无.text)和常量区(.ro.data)。
- .data和.bss无本质区别,.data存放的是显示初始化为非0的静态数据,.bss存放的是显示初始化为0或未显示初始化的静态数据。
- 特殊的数据会被放到代码段,比如常量字符串。这就是字符数组和字符串的区别。字符数组存储在数据段,字符串分配在代码段。
- 使用堆存储需要注意的问题:malloc返回的是void*类型的指针,void类型表示万能类型,malloc获取的内存指针使用前一定要检查是否为NULL,使用完记得手动释放。
栈空间用于开辟局部变量空间,实现自动内存管理。堆内存需要使用malloc和free手动申请和释放。静态数据区的数据段专门用于开辟全局变量和静态变量。
- 如果只在函数内部临时使用,定义局部变量;
- 堆内存和数据段几乎拥有相同的属性,但是他们的生命周期不同。堆是从malloc开始到free结束。静态变量是从程序一开始执行,直到整个程序结束。
2. 字符串和字符数组
- 高级语言中有独立的字符串类型,大多用String关键字来表示。但由于C语言中没有String类型,所以字符串类型是通过字符指针来间接实现的。关于字符串和字符指针请参考文章。
- 字符串本质上和字符数组没有什么区别,只是使用'\0'字符作为结尾符。(这里注意区分'\0'、'0'和0。'\0'的ASCII码为0,'0的ASCII码为48')
- 因为常量字符串存储在代码段(只读),字符数组存放在数据段。所以在改变字符串时,字符数组可以改变数组元素的内容,但是字符指针只能改变指针的值,让它指向新的字符串。
char *p = "linux";//字符串
sizeof(p)//返回4个字节,测试的是字符指针变量p本身的长度 4
strlen(p)//计算字符串中字符个数(不包含'\0') 5
char a[] = "linux";//字符指针
sizeof(a)//数组的字节数(包含'\0') 6
stelen(a)//计算字符串的长度(不包含'\0') 5
- 字符串和字符数组的本质差异: 对于字符串而言,字符指针p占4个字节,分配在栈上,字符串"linux"分配在代码段中,然后把代码段中字符串的首地址赋值给p。字符数组自带内存空间,可以直接用来存放字符数据。
- 为什么要sizeof?首先sizeof是C语言中的一个关键字,也是运算符,不是函数。原因一是像int、double等原生类型占用字节数和平台有关,原因二是用户自定义类型占用字节数无法一眼看出。
- 为什么要strlen库函数?因为字符串的定义中无法直接知晓字符串的长度,特别是当字符串中包含的字符个数很多时。需要注意:strlen返回的字符串长度不包含字符'\0'。
3. 结构体struct
先回顾之前的内容【C语言】结构。
- 数组的缺陷:一是定义时必须明确给出大小,且这个大小在以后不能再更改。二十数据要求所有的元素类型必须一致。结构体是用来解决数组的第二个缺陷的。
- 元素或成员访问方式:数组元素的访问方式表面上看有两种,下标和指针方式。实质上都是指针方式。结构体对于成员的访问本质上还是使用地址进行访问。
结构体的对齐访问
#include <stdio.h>
struct student{
char c;
int b;
};
int main()
{
printf("%d\n",sizeof(struct student));
return 0;
}
结果是8。为什么?
结构体要考虑元素的对齐访问,结构体实际占用的字节数与所有成员占用的字节数的总和不一定相等。32位编译器,一般编译器默认对齐方式是4字节对齐。结构体中每个元素本身需要对齐存放,结构体对齐后的大小必须为4的倍数。上述变量c占4个字节,int占4个字节。
为什么要对齐访问?
硬件本身有物理上的限制,对其排布和访问可以提高访问效率。
除了编译器自动对齐之外,也可以进行手动对齐。以#prgama pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对其参数就是n。GCC支持手动对齐(GCC中对齐指令_arrtibute_((packed))、_attribute_((aligned(n)))),如果不是有特殊需求的话,不建议使用。
offsetof宏(计算结构体中某个元素相对结构体首字节地址的偏移量)和container_of宏(知道结构体中某个成员的指针,反推这个结构体变量的指针)的使用,工作原理。
4. 共用体union
- 共用体中的各个成员其实是一体的,彼此不独立,它们使用同一个内存单元。同一个内存空间有多种解释方式。
- 使用sizeof测到的大小是union中各个元素里面占用内存最大的那个元素的大小。
- union中的元素不存在内存对齐的问题。
- C语言其实可以没有共用体,用指针和强制类型转换就可以替代共用体完成同样的功能,但是共用体简单,好理解。
5. 大小端模式
高字节对应地地址(大端模式),高字节对应高地址(小端模式)
测试当前机器的大小端。
#include <stdio.h>
union myunion{
int a;
char b;
};
int is_little_endian(void)
{
union myunion u1;
u1.a = 1;
return u1.b;
}
int main()
{
int i = is_little_endian();
if(1 == i)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
6. 枚举enum
为什么需要枚举?
编程时可以看符号而不用看数字。符号的意义是显而易见的,而数字代表的含义需要看文档或注释。C语言没有枚举也是可以的。
宏定义是不用数字而用符号。宏定义最先出现,用来解决符号常量的问题,后来人们发现有时候定义的符号常量之间有关联,于是发明了枚举。
对于枚举符号常量来说,数字不重要,符号才重要。