1.c错误处理
2.环境表
3.内存管理
1.C语言的错误处理
如:int main()
{
return 0;//表示程序正常结束
return -1;//表示程序异常结束
}
1.1C语言中通过返回值来表示错误形式,一般规则如下;
(1)如果函数的返回值类型是int类型,并且函数的返回值不可能是负数时,则返回0表示正常结束,返回-1表示异常结束
(2)如果函数的返回值类型是int类型,函数的返回值可能是负数时,使用指针作为函数的形参将函数的结果带出去,然后使用0表示正常结束,使用-1表示异常结束
(3)如果函数的返回值类型是指针类型,则返回NULL表示失败,其他值表示正常结束
(4)如果不考虑是否出错的情况,则返回类型使用void
练习:变成实现以下四个函数,具体功能如下:
a 返回1-10之间的随机数,如果随机数是5,则返回错误
/返回1~10之间的随机数
int rand_num(void)
{
//1.设置随机种子
srand(time(0));
//生成1~10之间的随机数
int res=rand()%10+1;
//3判断是否等于5,然后返回
return 5==res ?-1:res;
}
///b计算两个整形参数的最大值,如果相等,则返回错误
max_num(int a,int b,int*pmax)
{
if (ia==ib)
{
return -1;//表示出错
}
//int*pamx=&res;
//int*pmax; pmax=&res;
//*pmax=*&res=res;
*pmax = ia > ib ?ia :ib;
return 0;
}
c传入一个字符串,如果传入的字符串是“error”,则返回错误,否则返回“ok”
char* str_deal(char* pc)
{
return !strcmp(pc,"error")?NULL:"ok";
//return strcmp(pc,"error")?"ok":NULL;//strcmp 判断pc与error是否相等,相等为0 ;不相等为1;
}
//d打印传入的字符串即可
void print (char*pc)
{
printf("传入的字符串是:%s\n",pc);
}
int main(void)
{
printf("生成的随机数是:%d\n",rand_num());
return 0;
int res =0;
if (0==max_num(-1,-2,&res))
{
printf("最大值是:%d\n",,res);
}
printf("字符串的处理结果:%s\n",str_deal("hello"));//ok
print("good good study ,day day up");
return 0;
}
//错误编号和错误信息
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
//externn int errno;声明外部的全局变量
int main(void)
{
printf("函数调用之前errno=%d\n",errno);
printf(“错误信息是:%s\n”,strerror(errno));
FILE* fp=fopen("/etc/passwd","r");
if (NULL==fp)
{
printf("文件打开失败");
printf("第一次:errno=%d\n",errno);
printf(“错误信息是:%s\n”,strerror(errno))打印错误信息
perror("fopen");打印错误信息
printf("%m\n");打印错误信息
exit(-1)
}
printf(“文件打印成功\n”);
fclose(fp);
return 0;
}
2错误信息
strerror函数
perror函数打印 错误信息
//思考:能否直接使用errno来表示函数的调用是否出错呢?为什么?
注意:errno可以用于获取错误的原因,但不能用于判断函数调用是否成功的依据,因为errno是一个全局变量,一旦程序中出现错误,则会修改errno的值,那么后续代码中无论是否出错,errno都会有一个值,可能是之前的值
综上所述:判断函数的调用是否成功,还得依据函数的返回值,而确定出错的情况下,可以依据errno获取错误的原因
FILE* fp2=fopen("/etc/passwd","r");
if (errno)//errno有可能是上次错误的值,所以不能直接使用errno来表示函数调用是否出错。
{
printf("第二次:errno=%d\n",errno);
perrno("fopen2");
}
2环境表的概念和使用
2.1环境表的基本概念
环境表主要是指环境变量的集合,每个进程中都有一个环境表,用于记录与当前进程相关的环境变量信息
环境表采用字符指针数组的形式进行储存,然后使用全局变量char** envrion来记录环境表的首地址,使用NULL表示环境表的末尾
PATH=/bin:/usr/include:.... 0x01 char* pc1 ;pc1代表字符串的首地址
CPATH=/home/tarena:.... 0x02 char* pc2;
LIBRARY_PATH=/home/tarena/ 0x03 char* pc3;
环境表:采用字符指针数组来表示
字符指针数组:数组中的每一个元素都是一个字符指针
char* arr[4] ={0x01,0x02,0x03,NULL};
char (*arr)[4]; arr首先是一个指针,一个指向数组的指针
环境表:首地址 NULL表示结尾
char**environ 用于记录环境表的首地址(二级指针,指针的指针,指针的首地址)
环境表的结尾使用NULL表示
getenv()=>获取指定的环境变量值
setenv()=>修改、增加指定的环境变量
putenv()=>修改、增加指定的环境变量
unsetenv=>删除指定的环境变量
clearenv()=>清空环境表中的所有环境变量
环境表:采用字符指针数组来表示
char *arr[4] = {0x01,0x02,0x03,NULL};
环境表: 首地址 + NULL表示结尾
char ** environ 用于记录环境表的首地址也就是0x01的地址;
环境表的结尾使用NULL表示
解析:environ = arr =&pc1 = &0x01;
*environ = *(arr) = *(&pc1) = pc1= 0x01;
#include<stdio.h>
#include<>
#include<>
int main(void)
{
//声明二级指针为外部变量
extern char** envrion;
//寻找一个替身进行遍历
char** pe = environ;
while( *pe !=NULL)
{
//打印指向字符串形式的环境变量
printf("%s\n",*pe);
//指向下一个环境变量
pe++;
}
//练习:将环境表中的名字为SHELL的环境变量值摘取出来,放到自己定义的buf数组中,然后打印数组中的内容
printf("-----------------------------------------------------------------\n");
pe = environ;
char buf[20] = {0};
while(*pe !=NUll )
//判断是否是SHELL
if(strncmp(*pe,"SHELL=",6))
{
//把等号后面的内容放到数组中
strcpy(buf,*pe+6);
//跳出循环
break;
}
//指向下一个继续比较
pe++;
}
printf("buf = %s\n",buf);
return 0;
}
2.2相关函数
1)getenv()
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char* ps = getenv("SHELL");//获取环境变量SHELL的值
if(NULL = ps)
{
perror("gettenv"),exit(-1);
}
printf("SHELL = %s\n",ps);
return 0;
}
2)setenv函数
int setenv(const char *name,const char *value,int overwrite);
第一个参数:环境变量名
第二个参数:环境变量值
第三个参数:是否修改的标志
返回值:
函数功能:主要用于 修改/增加 环境变量
2.3main 函数的原型
int main (int argc,char* argv[ ],char* envp[ ])
第一个参数;命令行参数的个数
第二个参数:指针数组,存储命令行参数的地址
第三个参数:指针数组,环境表的首地址
//main函数的使用
06main.c
3.内存管理
3.1程序和进程的概念
程序 - 主要指在硬盘上的可执行文件
进程 - 主要指在内存中运行的程序
3.2 进程中的内存区域划分
int num;=>全局变量没有初始化 初始值为零 所在区域 BBS段
int main(void)
{
int num;=>局部变量 没有初始化 初始值就是随机数 所在区域 栈区
return 0;
}
代码区 -存储功能代码 ,函数名所在的区域
只读常量区, -存放字符串常量,以及const修饰的全局变量
全局区/数据区 -存放已经 初始化 的全局变量和static 修饰的局部变量
BBS段 -存放 没有初始化 的全局变量和静态局部变量,该区域会在main函数执行之前进行自动清零
堆区 -使用malloc/calloc/realloc/free 函数处理的内存,该区域的内存需要程序员手动申请和手动释放
栈区 -存放局部变量(包括函数的形参)、const修饰的局部变量,以及块变量,该区域的内存由操作系统负责分配和回收,程序员尽管放心使用即可
//进程中内存区域的划分
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int i1 =10;//全局区
int i2 =20;//全局区
int i3; //BBS段
const int i4 =40;//只读常量区
void fn(int i5)//桟区
{
int i6 =60;//桟区
static int i7=70;//全局区
const int i8=80;//桟区
int* p1 =(int*)malloc(sizeof(int));//堆区
int* p2 =(int*)malloc(sizeof(int));//堆区
char* str ="good";//只读常量区
char strs[] ="good";//桟区
printf("只读常量区:&i4=%p\n",&i4);
printf("只读常量区:str=%p\n",str);
printf("------------------------\n");
printf("全局区:&i1 = %p\n",&i1);
printf("全局区:&i2 = %p\n",&i2);
printf("全局区:&i7 = %p\n",&i7);
printf("------------------------\n");
printf("BBS段:&i3 = %p\n",&i3);
printf("------------------------\n");
printf("堆区:p1 = %p\n",p1);
printf("堆区:p2 = %p\n",p2);
printf("------------------------\n");
printf("桟区:&i5 = %p\n",&i5);
printf("桟区:&i6 = %p\n",&i6);
printf("桟区:&i8 = %p\n",&i8);
printf("桟区:strs = %p\n",strs);
printf("桟区:&i5 = %p\n",&i5);
}
int main(void)
{
printf("代码区:fn = %p\n",fn);
printf("------------------------\n");
fn(10);
return 0;
}
3.1程序和进程的概念
程序 - 硬盘上的可执行文件
进程 - 在内存中运行的程序
3.2 进程中内存区域的划分
代码区 只读常量区 全局区/数据区 BBS 段 堆区 栈区
今天的内容1内存管理
1.1内存区域的划分
代码区 只读常量区 全局区/数据区 BBS 段 堆区 栈区
1。地址按照从小到大 :代码区 只读常量区 全局区/数据区 BBS 段 堆区 栈区
2.代码区和只读常量区一般统称为代码区;其中全局区、数据区和BSS段一般统称为全局区、数据区
3.栈区和堆区没有严格的分割线,可以进行微调,并且堆区的分配一般按照从小到大进行分配,而栈区的分配一般按照地址从大到小进行分配
命令行参数环境变量 | |
地址大 | |
栈区 | 栈区和堆区没有严格的分割线 |
堆区 | 堆区的分配一般按照从小到大进行分配,而栈区的分配一般按照地址从大到小进行分配 |
全局区/数据区 | |
只读常量区 | |
代码区 | 地址小 |
//字符串的存储形式之间的比较
1.2字符串的存储形式之间的比较
对于指向常量字符串的字符指针和字符数组来说,字符指针可以改变指向,不可以改变指向的内容;对于字符数组来说,可以改变指向的内容,不可以改变指向;
对于存放常量字符串的堆区来说,指针的指向和指针指向的内容都可以改变
程序:UNIXC01-Unit04补。
1.3unix/linux系统中的内存都是采用虚拟内存管理技术进行管理的,即每个进程都有0~4G的内存地址(虚拟的,并不是真实存在的),由操作系统负责把虚拟内存地址和真实的物理内存映射起来,因此,不同的进程中虚拟地址空间看起来是相同的,但是对应的物理内存是不同的
其中0~3G之间的虚拟地址空间叫做用户空间,其中3G~4G之间的虚拟地址空间叫做内核空间;用户程序一般运行在用户空间,内核空间只有系统内核才可以访问,用户程序不能直接访问内核空间,但是系统内核提供了一些系统函数负责将程序从用户空间切换到内核控件执行,执行完毕之后再切换回到用户空间
内存地址的基本单位是字节,内存映射的基本单位是内存页,目前主流的操作系统的内存页大小是4kb(4096字节)
1mb =1024kb
1kb =1024Byte(字节)
1Byte =8bit(二进制位)
1.4段错误的由来
1)scanf函数的使用
scanf(“%d”,&num)
=>scanf("%d",num);
=>引发段错误
2)指针的使用
int* pi;=>野指针
int* pi=NULL;空指针
scanf(“%d”,pi);
=>引发段错误
3)使用未映射的虚拟内存地址
vim a.c 文件
int num=10;
printf("&num = %p\n",&num);//0x01
vim b.c文件
int* pi= 0x01;
printf("*pi = %p\n",,*pi);=>段错误
4)对没有操作权限的内粗进行操作
对只读常量区进行 操作,可能引发断错误
1.5使用malloc函数申请动态内存
1)注意事项
使用malloc函数申请内存时,除了申请所需要的内存大小之外,可能还会申请额外的4/8、12、16个字节,用于存储一些管理内存的相关信息,比如内存的大小等等
使用malloc函数申请内存时,一定要注意不要对所申请的内存空间进行越界访问,避免造成数据结构的破坏
2)使用malloc申请内存大小的一般原则
一般来说,使用malloc申请比较小的动态内存时,操作系统会一次性分配33个内存页的大小,最终的目的就是为了提高效率而已,1个内存页1024*4个字节
#include<unistd.h>
getpid()
=>获取当前进程的进程号
cat / proc / 进程号 / maps
=>查看指定进程的内存分配情况
size ./a.out
结果如下:
text (代码区)data(数据区) bss(BSS段)
1305 264 8
dec(十进制总和) hex(十六进制总和)
1622 656
filename(文件名)
a.out
1.6
使用free函数释放动态内存
注意事项
一般来说,使用malloc申请比较大的内存页时,系统会分配34个内存页,当所申请的内存页超过34个内存页时,系统给会再次分配33 个内存页(也就是按照33个内存页为基本单位分配)
而对于使用free释放内存时,则释放多少就减少多少,当使用free释放完毕所有内存时,系统可能会保留33个内存页以备再次申请使用,以此提高效率哦
#include<stdio.h>
#include<stdlib.h>
#include<.unistd.h>
int main(void)
{
printff("PID = %d \n",getpid());
int* p1 = (int*)malloc(4096);//一个内存页的空间申请
printf("申请了一个内存页的空间,p1 = %p\n"p1);
getchar();
int* p2 = (int*)malloc(30*4096)
printf("再次申请30个内存页,还是没有超过33个内存页的范围,p2 = %p\n",p2);
int* p3 =(int*)malloc (3*4096);
printf("再次申请3个内存页,超过了33个内存页的范围,p3=%p\n",p3);
getchar();
printf("-----------------------------------------\n");
free(p3);
p3=NULL;
printf("释放了3个内存页,还有31个内存页\n");
getchar();
free(p2);
p2=NUll;
printf("释放30个内存页,还有一个内存页\n");
getchar();
free(p1);
p1=NUll;
printf("释放一个内存页,");
getchar();
return 0;
}
1.7内存管理的相关函数
1)getpagesize函数
函数功能
主要用于获取当前系统中内存页的大小,一般为4kb=4*1024
1kb =1024Byte(字节)
1Byte =8bit(二进制位)
2)sbrk函数
注意:一般来说,使用sbrk申请比较小的内存时,系统默认分配一个内存页的大小,一旦申请的内存超过一个内存页时,则再次分配一个内存页(也就是按照一个内存页为基本单位进行分配),而释放内存时,如果释放之后剩下的内存足够用一个内存页表示,则一次性释放一个内存页
使用sbrk申请内存时,不会申请额外的空间存储管理内存的相关信息
使用sbrk函数申请内存比释放内存更加方便
3)brk函数
int brk(void *addr);
函数功能:
主要用于根据参数指定的目标位置调整内存大小
如果目标位置 > 之前的目标位置 =>申请内存
如果目标位置 < 之前的目标位置 =>释放内存
如果目标位置 = 之前的目标位置 =>
注意:
使用brk函数释放内存比较方便,因此一般情况下都使用sbrk函数和brk函数搭配使用,使用sbrk函数负责申请内存,使用brk函数负责释放内存
作业:使用sbrk函数申请内存,使用brk函数释放内存,首先申请一个int类型大小的空间,存放数据100;在申请一个double类型大小的空间,存放数据3.14;最后申请一个长度为10 个字节的字符空间,存放数据“hello”;要求打印上述三个变量的内存,最后释放所有的内存
明日预报:内存管理、文件管理