一、复习一下
1.指针的概念?
- 存储地址的基本数据类型
2.什么是数据类型?
- 在内存空间上框出一定空间的模子,比如int在内存空间上框出4个字节,int就是基本的数据类型
3.基本数据类型,多个数据类型,多个同样的基本数据类型?
- 基本数据类型:类似于int,float,char等,c语言内部已经定义好(包括大小和名字)的模子
- 多个数据类型:有多个基本数据类型组成的连续空间,比如struct结构体
- 多个同样的数据类型:多个同样的基本数据类型组合成的连续空间,c语言认为这是数据结构要研究的东西,不属于c语言的研究范围,所以c里面没有这个概念。
- 这里涉及到一个概念叫语法糖,编译器帮程序员封装好的一些内存访问和内存设计的行为。
二、数组
1.定义: 多个同样数据类型组成的连续空间。
- 数组名是首地址标记,标签,是地址常量,不能被赋值,只能初始化。
- 数组不是动态生成的,而是程序一旦编译好后,就已经固定的
2.初始化和赋值:数据一旦初始化便不可以改变,赋值可以改变数据
//赋值
int arr[3];
arr={10,20,30};//赋值{}常量空间、连续空间的语法糖
//初始化
int arr[3]={10,20,30};
3.默认和重载:相同的东西在不同的语境下表达的意思不同,比如运算符“+”的默认和重载,在默认语境下是数学意义上的加法(默认),在字符串语境下是字符串的连接(重载)。
int a=10+b;//加法行为 默认定义好的 c语言
//运算符重载 用户自定义 c++
?4.约束空间的结束标志(字符,非字符):
- 字符连续空间:0 双引号" "自带一个结束标志0。
-
void test03(){ int a=0x616263; printf("%x\n",a); //字符连续空间 // 无结束标志 char b[]={[0]=0x64,[1]='c',[2]='b',[3]='a'};//没有结束标志,输出dcbacba printf("%s\n",b); //有结束标志 char c[]={[0]=0x64,[1]='c',[2]='b',[3]='a',[4]=0};//有结束标志,输出dcba printf("%s\n",c); }
- 非字符连续空间:读到0时不会停止,但必须要有长度。比如读取xls文件时,遇到0并不会停止读取数据,但xls 文件有长度限制。
5.为什么需要约束空间的结束标志?
- c语言一旦知道了地址就可以任意访问,为了保证数据的正常访问,需要约束空间的结束标志。
6.C语言的维护连续空间的方法(字符,非字符):
字符连续空间 按照字节逐一处理,并且由语言来约定结束标志是0
非字符连续空间 按照任意长度来处理,结束标志没有要求,但必须约定长度
7.strcpy和memcpy:
- strcpy():是字符类型连续空间,有两个参数,没有长度参数
- memcpy(): 非字符类型连续空间,有三个参数,有长度参数(即约束空的间的结束标志)
8.C语言就是玩内存,内存管理大师
9.sizeof大小:
int a,b,c;
struct abc a1,a2,a3;
int c[5]; sizeof(c)=20 //所有int元素的大小5*4
sizeof(c[2])=4//一个int元素的大小
sizeof(c+2)=8 //首地址偏移2个元素地址后的地址的大小
int *d; sizeof(*d)=8//指针(即地址)的大小,一般是4位或8位,由操作系统决定
//写成代码
test06(){
int c[5];
int *d;
//指针(即地址)的大小,一般是4位或8位,由操作系统决定
printf("c: %p\n", (void*)&c); // 打印数组c的地址
printf("c[2]: %p\n", (void*)&c[2]); // 打印数组c中第三个元素的地址
printf("c+2: %p\n", (void*)(c+2)); // 打印数组c首地址偏移2个int元素后的地址
printf("*d: %p\n", (void*)d); // 打印指针d指向的地址的内容
printf("Size of c: %zu\n", sizeof(c)); // 打印数组c的大小
printf("Size of c[2]: %zu\n", sizeof(c[2])); // 打印数组c中第三个元素的大小
printf("Size of c+2: %zu\n", sizeof(c+2)); // 打印数组c首地址偏移2个int元素后的大小
printf("Size of *d: %zu\n", sizeof(*d)); // 打印指针d指向的地址的内容的大小
}
10.?sizeof和strlen():
- sizeof():是字符类型连续空间的关键词,
- strlen():是非字符类型连续空间的函数,
三、内存的分段管理
1.经典的段错误(面试的时候可能会问到):
#include<stdio.h>
//经典段错误
void test01(){
char s[]="abcd";
//char*s="abcd";//经典段错误
s[1]='1';
printf("%s\n",s);//输出a1cd
//*(s+1)和s[1]是一个意思
*(s+1)='1';
printf("%s\n",s);//输出a1cd
}
int main(){
test01();
return 0;
}
2.越界访问就是段错误
3.分段
- 只读段:存储常量,代码,函数。只要程序不终止,永远存在。
- 读/写段:存储全局变量。只要程序不终止,永远存在。
- 段:
4.func1和func2的例子:
//典型错误
int *func1(){
int abc;//abc进栈
return &abc;//返回abc的时候, abc的地址已经还给系统(可以理解为,abc去别的地方了)
//此时&abc,取到的地址实际上已经不是abc的地址了
//return &abc可以理解为,abc的地址换了,但还是用原来的地址去找abc,所以找不到很正常)
}
//正确的例子
int *func2(){
//用malloc从堆区申请100字节,将首地址传给p
int *p=malloc(100);
return p;//返回p时,虽然p的地址返已经还给系统,
//但p传给函数的是malloc的首地址(不是变量*p的地址),
//而malloc此时还在,所以函数可以接收到malloc的地址
}
5.内存泄漏与内存溢出(面试时可能会问到):
- 内存泄漏:变量内存被释放后,越界访问变量地址。一直执行程序,没有释放内存
-
char *rec=func2();//申请内存 rec[x]=xxxxxxx;//利用内存 free(rec);//释放内存,如果一直不释放内存就会造成内存泄漏
- 内存溢出:
网卡传递消息 用户能接收的消息大小: char buf[10] //大小为10B 通过函数传递消息: ssize_t recv(参数1,void *buffer,参数2, 参数3,参数4) 网卡要传递的消息大小: 200B 这种情况下,网卡传递消息给用户,就形成了内存溢出
6.分段管理:每个段都有自己的权限,由操作系统分配:
- 一旦应用程序非法访问某个段,os(操作系统)就会终止这个程序,并抛出“segmentation fault”错误