8-1:
用read、write、open和close系统调用替代标准库中功能等价的函数,重写第七章的cat程序,并通过实验比较两个版本的相对执行速度
#include
#include
#define BUFSIZE 1000
int main(int argc, char **argv)
{
int fp;
void filecopy(int, int);
char *prog = argv[0];
if(argc == 1)
filecopy(0, 1);
else
while(--argc > 0)
if((fp = open(*++argv, O_RDONLY, 0)) == -1)
{
fprintf(stderr, "%s: can't open %s\n", prog, *argv);
return 1;
}
else
{
filecopy(fp, 1);
close(fp);
}
return 0;
}
void filecopy(int ifp, int ofp)
{
int n;
char buf[BUFSIZE];
while((n = read(ifp, buf, BUFSIZE)) > 0)
if(write(ofp, buf, n) != n)
fprintf(stderr, "error: write error\n");
}
8-2、8-3、8-4:
用字段代替显式的位操作,重写fopen和_fillbuf函数,比较相应代码的长度和执行速度
设计并编写函数_flushbuf、fflush和fclose
标准库函数
int fseek(FILE *fp, long offset, int origin)
类似于函数lseek,所不同的是,该函数中的fp是一个文件指针而不是文件描述符,且返回值是一个int类型的状态而非位置值。编写函数fseek,并确保该函数与库中的其他函数使用的缓冲能够协同工作
#include
#include
#define EOF (-1)
#define BUFSIZ 1024
#define OPEN_MAX 20
#define PERMS 0666
#define SEEK_SET 0//标准库中表示偏移起始位置为文件头
#define SEEK_CUR 1//标准库中表示偏移起始位置为当前位置
#define SEEK_END 2//标准库中表示偏移起始位置为文件尾
struct flags{
unsigned int _READ : 1;//只读
unsigned int _WRITE : 1;//只写
unsigned int _UNBUF : 1;//无缓冲
unsigned int _EOF : 1;//文件结尾
unsigned int _ERR : 1;//错误
};
typedef struct _iobuf{
int cnt; //缓冲区剩余字符数
char *ptr; //指向缓冲区下一字符的指针
char *base;//指向缓冲区首字符的指针
struct flags flag;//文件模式
int fd;//文件描述符
} FILE;
FILE _iob[OPEN_MAX] = {
{0, (char *) 0, (char *) 0, {1, 0, 0, 0, 0}, 0},//stdin,只读形式
{0, (char *) 0, (char *) 0, {0, 1, 0, 0, 0}, 1},//stdout,只写形式
{0, (char *) 0, (char *) 0, {0, 1, 1, 0, 0}, 2},//stderr,无缓冲写入形式
};
#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
#define feof(p) ((p) -> flag._EOF != 0)
#define ferror(p) ((p) -> flag._ERR != 0)
#define fileno(p) ((p) -> fd)
#define getc(p) (--(p) -> cnt >= 0 \
? (unsigned char) *(p) -> ptr++ : _fillbuf(p))
//刚开始执行时,缓冲区剩余字符数为0,调用_fillbuf将文件中的数据读取到缓冲区,然后每次返回一个字符
#define putc(x, p) (--(p) -> cnt >= 0 \
? *(p) -> ptr++ = (x) : _flushbuf((x), p))
//刚开始执行时,缓冲区剩余字符为0时,调用_flushbuf分配缓冲区,然后每次写入一个字符到缓冲区,直到cnt为0,
//调用_flushbuf将缓冲区字符写入文件中
//getc和putc的区别在于:getc是调用_fillbuf将文件中的数据读取到缓冲区,然后每次从缓冲区返回一个字符
//putc是先将字符写入缓冲区,然后调用_flushbuf将缓冲区的数据写入文件
#define getchar() getc(stdin)
#define putchar(x) putc((x), stdout)
FILE *fopen(const char *, const char *);
int _fillbuf(FILE *);//_fillbuf填充缓冲区,将文件的数据读取到缓冲区中
int _flushbuf(int, FILE *);//_flushbuf冲刷缓冲区,将缓冲区的数据写入到参数指定的文件中
int fflush(FILE *);//fflush函数强迫将缓冲区内的数据写回参数指定的文件中,如果参数为NULL,fflush()会将所有打开的文件数据更新
int fclose(FILE *);//fclose函数关闭一个文件流,并释放文件指针和相关的缓冲区
int fseek(FILE *, long, int);
int main(int argc, char **argv)
{
FILE *fp1, *fp2;
char c;
if(argc != 3)
return -1;
else
{
if((fp1 = fopen(argv[1], "r")) == NULL || (fp2 = fopen(argv[2], "w")) == NULL)
return -1;
else
{
while((c = getc(fp1)) != EOF)
{
if(c == '\n')
{
fseek(fp1, 5L, SEEK_CUR);
}
putc(c, fp2);
}
//请注意,如果fp1所指向文件中字符数小于BUFSIZ,那么执行完该循环时,fp2中并没有写入字符
//只有利用_flushbuf或者fflush函数才能将缓冲区的数据写入fp2所指向的文件,当然fclose也可以完成这一步
fclose(fp1);
fclose(fp2);
}
}
return 0;
}
FILE *fopen(const char *name, const char *mode)
{
int fd;
FILE *fp;
if(*mode != 'r' && *mode != 'w' && *mode != 'a')//没有以指定模式打开文件
return NULL;
for(fp = _iob; fp < _iob + OPEN_MAX; fp++)
if(fp -> flag._READ == 0 && fp -> flag._WRITE == 0)//在结构数组中查找有无未赋值的成员
break;
if(fp >= _iob + OPEN_MAX)//如果遍历结构数组未找到未赋值成员
return NULL;
if(*mode == 'w')//以只写形式打开文件
fd = creat(name, PERMS);//creat函数新建一个文件或者将现有文件长度截为0
else if(*mode == 'a')//以写的形式打开文件在文件末尾添加内容
{
if((fd = open(name, O_RDONLY, 0)) == -1)//没有该文件
fd = creat(name, PERMS);//新建文件
lseek(fd, 0L, 2);//定位到文件结尾
}
else//以只读形式打开文件
fd = open(name, O_RDONLY, 0);
if(fd == -1)//文件打开或者新建失败
return NULL;
fp -> fd = fd;
fp -> cnt = 0;
fp -> base = NULL;
fp -> flag._READ = (*mode == 'r') ? 1 : 0;//标注该文件以哪种模式打开
fp -> flag._WRITE = (*mode == 'r') ? 0 : 1;
return fp;
}
int _fillbuf(FILE *fp)
{
int bufsize;
if(fp -> flag._READ == 0 || fp -> flag._EOF == 1 || fp -> flag._ERR == 1)//文件不是以只读形式打开或到文件结尾或遇到错误
return EOF;
bufsize = (fp -> flag._UNBUF) ? 1 : BUFSIZ;//如果是无缓冲,则缓冲区大小为1,否则为BUFSIZ
if(fp -> base == NULL)//如果此前未分配缓冲区
if((fp -> base = (char *) malloc(bufsize)) == NULL)//如果分配缓冲区内存失败
return EOF;
fp -> ptr = fp -> base;//ptr指向缓冲区首字符
fp -> cnt = read(fp -> fd, fp -> ptr, bufsize);//read函数从文件中最大读取bufsize个字符到缓存区,并返回实际读取字符数
if(--fp -> cnt < 0)//如果实际读取字符数量小于等于0
{
if(fp -> cnt == -1)//实际读取为0时,说明遇到文件结尾
fp -> flag._EOF = 1;
else//实际读取为负值时,说明遇到文件错误
fp -> flag._ERR = 1;
fp -> cnt = 0;
return EOF;
}
return (unsigned char) *fp -> ptr++;//返回缓冲区首字符,ptr指向缓冲区下一字符
}
int _flushbuf(int c, FILE *fp)
{
int bufsize;
int write_number;//记录待写入的字符数
if(fp < _iob || fp >= _iob + OPEN_MAX)//如果指针不是指向结构数组的成员
return EOF;
if(fp -> flag._WRITE == 0 || fp -> flag._EOF == 1 || fp -> flag._ERR == 1)
return EOF;
bufsize = (fp -> flag._UNBUF) ? 1 : BUFSIZ;
if(fp -> base == NULL)//如果没有分配缓冲区
{
if((fp -> base = (char *) malloc(bufsize)) == NULL)
{
fp -> flag._ERR = 1;//如果分配缓冲区内存失败,标注错误
return EOF;
}
}
else
{
write_number = fp -> ptr - fp -> base;//待写入字符数为指向当前字符的指针减去指向首字符的指针
if(write(fp -> fd, fp -> base, write_number) != write_number)//write函数将缓冲区中的数据写入文件中,返回实际写入字符数
{
fp -> flag._ERR = 1;//如果实际写入字符数与待写入字符数不相等,说明遇到错误
return EOF;
}
}
fp -> ptr = fp -> base;//ptr指向缓冲区首字符
*fp -> ptr++ = (unsigned char)c;
fp -> cnt = bufsize - 1;
return c;
}
int fflush(FILE *fp)
{
int retval = 0;//记录返回值
if(fp == NULL)//如果fp为NULL,那么就要对结构数组的所有成员进行操作
{
for(int i = 0; i < OPEN_MAX; i++)
if(_iob[i].flag._WRITE == 1 && (fflush(_iob + i) == -1))//首先判断文件是否是以写的形式打开,如果是执行一次递归,如果返回值为-1
retval = -1;
}
else
{
if(fp -> flag._WRITE == 0)//如果文件不是以写的形式打开,直接返回
return -1;
_flushbuf(EOF, fp);//冲刷缓冲区,将缓冲区的数据强制写入fp所指向文件中,同时在缓冲区写入EOF
if(fp -> flag._ERR == 1)//如果出现错误
retval = -1;
}
return retval;
}
int fclose(FILE *fp)
{
int fd;//保存当前文件描述符
struct flags news = {0, 0, 0, 0, 0};
if(fp == NULL)
return -1;
fd = fp -> fd;
fflush(fp);//冲刷缓冲区
fp -> ptr = NULL;
if(fp -> base != NULL)
free(fp -> base);//释放缓冲区分配的空间
fp -> base = NULL;
fp -> flag = news;//重置文件模式
fp -> cnt = 0;
fp -> fd = -1;//文件描述符标注为-1
return close(fd);
}
int fseek(FILE *fp, long offset, int origin)
{
if(fp -> flag._UNBUF == 0 && fp -> base != NULL)//文件不是无缓冲,并且已分配缓冲区
{
if(fp -> flag._WRITE)//如果文件是以只写形式打开
{
if(fflush(fp))//冲刷缓冲区
return EOF;
}
else if(fp -> flag._READ)//文件以只读形式打开
{
if(origin == SEEK_CUR)//如果偏移起始位置为当前位置
{
if(offset >= 0 && offset <= fp -> cnt)//如果偏移量不超过缓冲区剩余字符大小,跳过待偏移部分的字符,然后返回
{
fp -> cnt -= offset;
fp -> ptr += offset;
return 0;
}
else//否则将偏移量与剩余字符的差值赋给偏移量
offset -= fp -> cnt;
}
fp -> cnt = 0;//重置缓冲区
fp -> ptr = fp -> base;
}
}
return lseek(fp -> fd, offset, origin);
}
8-5:
修改fsize程序,打印i节点项中包含的其它信息
/*
由于我的系统不是UNIX系统,所以这个程序我也不知道具体的运行结果,但是我输入文件名(不是目录)时,结果是正常的
UNIX系统中,目录是一个文件,它包含一个文件名列表和这些文件的i结点索引号,而i结点存储的是文件的相关信息(即一个stat结构)
也就是说一个目录可以看做是一个结构数组,该数组的每个元素是一个direct结构,结构包含一个文件名和该文件的i结点索引号
由于目录是一个文件,那么就可以使用open函数和read函数打开,传入目录名利用open函数得到文件描述符
传入文件描述符利用read函数每次从目录中读取出文件名和索引号(即一个direct结构)
由于目录中每个文件的文件名是没有空字符的,但是我们读取每个文件时,需要在文件名后加空字符,因此另外声明一个与direct结构
类似的结构Dirent
*/
#include
#include
#include
#include
#include
#include
//#include
#define DIRSIZ 14
struct direct {
ino_t d_ino;//索引号
char d_name[DIRSIZ];//没有空字符的名
};
#define NAME_MAX 14
typedef struct {
long ino;//索引号
char name[NAME_MAX + 1];//名加一个空字符
} Dirent;
typedef struct {
int fd;//文件描述符
Dirent d;
} DIR;
DIR *opendir(char *dirname);
Dirent *readdir(DIR *dfd);
void closedir(DIR *dfd);
void fsize(char *);
void dirwalk(char *, void(*fcn) (char *));
int main(int argc, char **argv)
{
if(argc == 1)
fsize(".");//处理当前目录
else
while(--argc > 0)
fsize(*++argv);
return 0;
}
void fsize(char *name)
{
struct stat stbuf;//储存i结点信息的结构,该结构的声明在头文件中
if(stat(name, &stbuf) == -1)//系统调用stat将name文件的i结点信息存储在stbuf中,如果返回值为-1,表示出错
{
fprintf(stderr, "fsize: can't access %s\n", name);
return;
}
if((stbuf.st_mode & S_IFMT) == S_IFDIR)//S_IFMT表示文件类型,S_IFDIR表示文件类型为目录
dirwalk(name, fsize);//遍历目录
printf("%ld\t%ld\t%d\t%d\t%d\t%d\t%ld\t%8ld\t%ld\t%ld\t%ld\t%s\n",
stbuf.st_dev, stbuf.st_ino, stbuf.st_mode, stbuf.st_nlink, stbuf.st_uid, stbuf.st_gid,
stbuf.st_rdev, stbuf.st_size, stbuf.st_atime, stbuf.st_mtime, stbuf.st_ctime, name);
}
void dirwalk(char *dir, void (*fcn) (char *))
{
char name[MAX_PATH];
Dirent *dp;
DIR *dfd;
if((dfd = opendir(dir)) == NULL)//opendir函数打开目录,并将信息保存在一个DIR结构中返回,如果出现错误,返回NULL
{
fprintf(stderr, "dirwalk: can't open %s\n", dir);
return;
}
while((dp = readdir(dfd)) != NULL)//直到整个目录遍历完
{
if(strcmp(dp -> name, ".") == 0 || strcmp(dp -> name, ".."))//如果返回的文件名为"."或"..",为目录本身和父目录,跳过
continue;
if(strlen(dir) + strlen(dp -> name) + 2 > sizeof(name))//文件名过长
fprintf(stderr, "dirwalk: name %s %s too long\n", dir, dp -> name);
else
{
sprintf(name, "%s/%s", dir, dp -> name);
(*fcn)(name);//递归调用,查看文件是否为目录,如果为目录,继续递进,否则递归回归,继续下一文件
}
}
closedir(dfd);
}
DIR *opendir(char *dirname)
{
int fd;//文件描述符
struct stat stbuf;//储存i结点信息的结构
DIR *dp;
if((fd = open(dirname, O_RDONLY, 0)) == -1//open函数返回文件描述符
|| fstat(fd, &stbuf) == -1 //系统调用fstat打开文件,并将文件的i结点信息存储在stbuf中
|| (stbuf.st_mode & S_IFMT) != S_IFDIR//如果文件类型不是目录
|| ((dp = (DIR *) malloc(sizeof(DIR))) == NULL)//如果分配内存失败
)
return NULL;
dp -> fd = fd;
return dp;
}
void closedir(DIR *dp)
{
if(dp)
{
close(dp -> fd);//关闭文件
free(dp);//释放分配的内存
}
}
Dirent *readdir(DIR *dp)
{
struct direct dirbuf;
static Dirent d;//d是一个static变量,每次退出函数不会销毁,用于储存目录中一个文件的文件名和索引号
while(read(dp -> fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf))//每次从目录中读取一个direct结构的信息
{
if(dirbuf.d_ino == 0)//如果目录位置当前未使用,跳过,读取下一个
continue;
d.ino = dirbuf.d_ino;//将索引号与文件名拷贝
strncpy(d.name, dirbuf.d_name, DIRSIZ);
d.name[DIRSIZ] = '\0';//在文件名后添加空字符
return &d;
}
return NULL;//如果目录中的信息读取完,就返回NULL
}
利用一个简单的例子来解释上面的递归调用:假设有一个待访问目录G,其包含的文件如下
首先将目录G传入fsize函数,判断其是一个目录
递归递进,将目录G传入dirwalk函数,opendir函数得到目录G的文件描述符,readdir函数返回目录G的第一个文件信息,即目录A
递归递进,将目录A传入fsize函数,判断其是一个目录
递归递进,将目录A传入dirwalk函数,得到目录A的第一个文件信息,即目录D
递归递进,将目录D传入fsize函数,判断其是一个目录
递归递进,将目录D传入dirwalk函数,得到目录D的第一个文件信息,即文件7
递归递进,将文件7传入fsize函数,打印文件7的信息
递归回归,到dirwalk函数,继续执行循环,目录D处理完,readdir函数返回NULL,closedir函数删除为目录D分配的储存信息空间
递归回归,到fsize函数,打印目录D的信息
递归回归,到dirwalk函数,继续执行循环,得到目录A的第二个文件信息,即目录E
递归递进,到fsize函数,判断为一个目录
递归递进,到dirwalk函数,得到目录E的第一个文件信息,即文件8
递归递进,到fsize函数,打印文件8的信息
递归回归,到dirwalk函数,继续执行循环,目录E处理完,readdir函数返回NULL,closedir函数删除为目录E分配的存储信息空间
递归回归, 到fsize函数,打印目录E的信息
递归回归,到dirwalk函数,继续执行循环,得到目录A的第三个文件信息,即文件4
递归递进,到fsize函数,打印文件4的信息
递归回归,到dirwalk函数,目录A处理完,closedir函数删除为目录A分配的存储信息空间
递归回归,到fsize函数,打印目录A的信息
递归回归,到dirwalk函数,处理目录G剩下的文件…
此递归与我们之前在第五章所学的dcl程序本质上差不多,所以在此,不在详述,可参看我之前发的第五章习题解答第18题
第8章——存储分配程序
由于整个程序我学起来很吃力,所以单独分出来解析一下
/*空闲块以链表的形式进行管理,每个块包含一个Header大小的头部,以及n*Header大小的空闲空间,malloc函数返回的是空闲空间(不包含头部)
头部包含的信息是指向下一个空闲块的指针ptr,以及一个记录块大小的unsigned数size,也就是说当前块的上一块的ptr,其实是指向当前块的指针
*/
#include
#include
#define NALLOC 1024
typedef double Align;//该系统中double为最受限类型
union header {
struct {
union header *ptr;
unsigned size;
} s;
Align x;
};//header联合的大小为所占空间最大的成员的大小,即最受限类型的大小
typedef union header Header;
static Header base;//base是整个空闲块链表的头部
static Header *freep = NULL;//freep指向上一次完成操作时的块的上一块
void *malloc(unsigned nbytes);
static Header *moreroce(unsigned nu);
void free(void *ap);
int main(void)
{
int *p;
int i;
if((p = (int *) malloc(100 * sizeof(int))) == NULL)
return -1;
for(i = 0; i < 100; i++)
*(p + i) = (i + 1) * 2;
for(i = 0; i < 100; i++)
{
printf("%d", *(p + i));
if(i % 10 == 9)
putchar('\n');
}
free(p);
return 0;
}
void *malloc(unsigned nbytes)
{
Header *p, *prevp;//p指向当前块,prevp指向上一块
Header *moreroce(unsigned);
unsigned nunits;//实际所需空间大小,以一个Header大小为单位
nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
//以向上舍入(大于等于该数的最小整数)的方式计算实际需求的空间大小,加上一个Header大小是因为C中的除法是采用向下舍入的方式计算结果
//为了防止nbytes是Header大小的整数倍时,向上舍入所得结果会比预期值大1,因此还需要进行-1操作。商的结果+1表示一个头部
if((prevp = freep) == NULL)//每次进入malloc函数时,将prevp指向上一次退出函数时返回的块的上一块,如果是第一次进入该函数,那么就为NULL
{
base.s.ptr = prevp = freep = &base;//第一次进入函数时设置freep和prevp指向base,同时base的ptr成员指向自身,构成一个闭合的链表
base.s.size = 0;
}
for(p = prevp -> s.ptr; ; prevp = p, p = p -> s.ptr)//更新条件为当前块变成上一块,而当前块的下一块变为当前块
{
if(p -> s.size >= nunits)
{
if(p -> s.size == nunits)
prevp -> s.ptr = p -> s.ptr;//让上一块的ptr直接指向当前块的下一块
else
{
p -> s.size -= nunits;//当前块的大小减少units
p += p -> s.size;//返回当前块的尾部
p -> s.size = nunits;//标准返回的块的大小
}
freep = prevp;//保留返回块的上一块的指针
return (void *)(p + 1);//只返回当前块的空闲空间,不返回头部
}
if(p == freep)//p是从freep所指的块的下一块开始的,如果再次回到freep,说明对整个链表已完成一轮搜寻,没有找到大于等于所需空间的块
if((p = moreroce(nunits)) == NULL)//调用moreroce函数请求系统分配空间,并让p指向分配的块的上一块
return NULL;
}
}
static Header *moreroce(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if(nu < NALLOC)//一次调用至少分配NALLOC个Header大小的空间
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if(cp == (char *) -1)//sbrk返回-1时,返回NULL
return NULL;
up = (Header *) cp;//以Header大小为单位对分配的空间进行处理
up -> s.size = nu;
free((void *) (up +1));//设置完头部之后,调用free函数将头部后面的空闲空间插入到空闲区域
return freep;//完成free操作后,freep指向的是分配块的上一块
}
void free(void *ap)
{
Header *bp, *p;//bp指向待释放块,p指向当前块
bp = (Header *) ap - 1;//回到块的头部
for(p = freep; !(bp > p && bp < p -> s.ptr); p = p -> s.ptr)//由于块的排列是根据块的地址递增顺序,从freep所指的块开始循环
//直到待释放块的地址大于当前块并且小于当前块的下一块时退出循环
if(p >= p -> s.ptr && (bp > p || bp < p -> s.ptr))//还有一种情况是,待释放块的地址大于链表的最后一块,或者小于链表的第一个块
break;
if(bp + bp -> s.size == p -> s.ptr)//如果待释放块紧邻当前块的下一块
{
bp -> s.ptr = p -> s.ptr -> s.ptr;//将待释放块的ptr指向当前块的下一块的下一块
bp -> s.size += p -> s.ptr -> s.size;//合并待释放块和当前块的下一块
}
else
bp -> s.ptr = p -> s.ptr;//否则将待释放块的ptr指向当前块的下一块
if(p + p -> s.size == bp)//如果当前块与待释放块紧邻
{
p -> s.ptr = bp -> s.ptr;//将当前块的pr指向待释放块的下一块
p -> s.size += bp -> s.size;//合并当前块和待释放块
}
else
p -> s.ptr = bp;//否则将当前块的ptr指向待释放块
//如果待释放与当前块和当前块的下一块都不紧邻,那么就先执行第一个else,再执行第二个else,同时待释放块的size不改变
//两个if-else语句不能调换顺序
freep = p;//freep指向当前块
}
/*整个程序最难的就是第一次执行malloc函数时的操作,所以重点解释该部分:
首先声明一个base,是整个空闲块链表的头部,其成员ptr需要指向空闲块链表的第一个块,而空闲块链表的最后一块成员ptr指向base
这样就能实现一个闭合的链表。
首次指向malloc函数时,(prevp = freep) == NULL,将prevp和freep指向base,而base成员ptr指向base,也就是说freep -> s.ptr ==
prevp -> s.ptr == freep == prevp == &base
进入到for循环,(p = prevp -> s.ptr) == freep,调用moreroce函数,系统分配一个大于等于NALLOC * sizeof(Header)大小的空间
并将该空间作为一个块,调用free函数该块的空闲空间插入到空闲区域
在free函数中,bp指向该块的头部,而p = freep,即p指向的是base,那么p -> s.ptr == &base,也就是说p和p -> s.ptr所指向的地址是一样的,
即p == p -> s.ptr,并且无论是bp > p或者是bp < p -> s.ptr,必然满足其中的一个条件,因为bp != p, if条件判断为真,跳出循环
由于p == p -> s.ptr == &base,而base.s.size == 0,并且bp != p,那么两个if-else语句的判断条件都为假,第一个else结果为
bp -> s.ptr == p -> s.ptr == &base,即bp所指的块的头部成员ptr指向base,第二个else结果为p -> s.ptr == base.s.ptr == bp,
即base成员ptr指向新分配的块,而freep == p == &base
moreroce函数将freep返回给malloc函数,(p = moreroce(units)) == freep == &base,循环更新,(prevp = p) == &base,
(p = p -> s.ptr) == base.s.ptr,即prevp指向base,而p指向第一个块,之后再将所需大小的空闲空间返回给主调函数,同时
(freep = prevp) == &base,对malloc函数的第一次调用完成
*/
8-6:
标准库函数calloc(n, size)返回一个指针,它指向n个长度为size的对象,且所有分配的存储空间都被初始化为0。通过调用或者修改malloc函数来实现calloc函数
/*不管是修改还是调用malloc函数,主要需要解决的是两个问题:
根据传递的参数计算所需空间大小、将分配的存储空间初始化为0 */
#include
#include
#include
#define NALLOC 1024
typedef double Align;
union header {
struct {
union header *ptr;
unsigned size;
} s;
Align x;
};
typedef union header Header;
static Header base;
static Header *freep = NULL;
void *calloc(unsigned n, unsigned size);
static Header *moreroce(unsigned nu);
void free(void *ap);
int main(void)
{
int *p;
int i;
if((p = (int *) calloc(100, sizeof(int))) == NULL)
return -1;
for(i = 0; i < 100; i++)
*(p + i) = (i + 1) * 2;
for(i = 0; i < 100; i++)
{
printf("%d", *(p + i));
if(i % 10 == 9)
putchar('\n');
}
free(p);
return 0;
}
void *calloc(unsigned n, unsigned size)
{
Header *p, *prevp;
Header *moreroce(unsigned);
unsigned nunits;
nunits = ((n * size) + sizeof(Header) - 1) / sizeof(Header) + 1;//计算所需空间大小
if((prevp = freep) == NULL)
{
base.s.ptr = prevp = freep = &base;
base.s.size = 0;
}
for(p = prevp -> s.ptr; ; prevp = p, p = p -> s.ptr)
{
if(p -> s.size >= nunits)
{
if(p -> s.size == nunits)
prevp -> s.ptr = p -> s.ptr;
else
{
p -> s.size -= nunits;
p += p -> s.size;
p -> s.size = nunits;
}
freep = prevp;
memset(p + 1, 0x00, n * size);//存储空间初始化方式有两种:利用循环、利用memset函数,后者相对方便一点
//memset函数将第一个参数为指针s,第二参数为待替换字符c,第三个参数为替换数量n
//memset函数将s中的前n个字符替换为c,并返回s
return (void *)(p + 1);
}
if(p == freep)
if((p = moreroce(nunits)) == NULL)
return NULL;
}
}
static Header *moreroce(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if(nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if(cp == (char *) -1)
return NULL;
up = (Header *) cp;
up -> s.size = nu;
free((void *) (up +1));
return freep;
}
void free(void *ap)
{
Header *bp, *p;
bp = (Header *) ap - 1;
for(p = freep; !(bp > p && bp < p -> s.ptr); p = p -> s.ptr)
if(p >= p -> s.ptr && (bp > p || bp < p -> s.ptr))
break;
if(bp + bp -> s.size == p -> s.ptr)
{
bp -> s.ptr = p -> s.ptr -> s.ptr;
bp -> s.size += p -> s.ptr -> s.size;
}
else
bp -> s.ptr = p -> s.ptr;
if(p + p -> s.size == bp)
{
p -> s.ptr = bp -> s.ptr;
p -> s.size += bp -> s.size;
}
else
p -> s.ptr = bp;
freep = p;
}
8-7:
malloc接受对存储空间的请求时,并不检查请求长度的合理性;而free函数则认为被释放的块包含一个有效的长度字段。改进这些函数,使他们具有错误检查的功能
#include
#include
#define NALLOC 1024
typedef double Align;
union header {
struct {
union header *ptr;
unsigned size;
} s;
Align x;
};
typedef union header Header;
static Header base;
static Header *freep = NULL;
void *malloc(unsigned nbytes);
static Header *moreroce(unsigned nu);
void free(void *ap);
int main(void)
{
int *p;
int i;
if((p = (int *) malloc(100 * sizeof(int))) == NULL)
return -1;
for(i = 0; i < 100; i++)
*(p + i) = (i + 1) * 2;
for(i = 0; i < 100; i++)
{
printf("%d", *(p + i));
if(i % 10 == 9)
putchar('\n');
}
free(p);
return 0;
}
void *malloc(unsigned nbytes)
{
Header *p, *prevp;
Header *moreroce(unsigned);
unsigned nunits;
if(nbytes <= 0)//malloc函数错误判断
{
fprintf(stderr, "error: Request space size must be a positive integer\n");
return NULL;
}
nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
if((prevp = freep) == NULL)
{
base.s.ptr = prevp = freep = &base;
base.s.size = 0;
}
for(p = prevp -> s.ptr; ; prevp = p, p = p -> s.ptr)
{
if(p -> s.size >= nunits)
{
if(p -> s.size == nunits)
prevp -> s.ptr = p -> s.ptr;
else
{
p -> s.size -= nunits;
p += p -> s.size;
p -> s.size = nunits;
}
freep = prevp;
return (void *)(p + 1);
}
if(p == freep)
if((p = moreroce(nunits)) == NULL)
return NULL;
}
}
static Header *moreroce(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if(nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if(cp == (char *) -1)
return NULL;
up = (Header *) cp;
up -> s.size = nu;
free((void *) (up +1));
return freep;
}
void free(void *ap)
{
Header *bp, *p;
bp = (Header *) ap - 1;
if(ap == NULL || bp -> s.size <= 0)//free函数错误判断
{
fprintf(stderr, "error: pointer is empty\n");
return;
}
for(p = freep; !(bp > p && bp < p -> s.ptr); p = p -> s.ptr)
if(p >= p -> s.ptr && (bp > p || bp < p -> s.ptr))
break;
if(bp + bp -> s.size == p -> s.ptr)
{
bp -> s.ptr = p -> s.ptr -> s.ptr;
bp -> s.size += p -> s.ptr -> s.size;
}
else
bp -> s.ptr = p -> s.ptr;
if(p + p -> s.size == bp)
{
p -> s.ptr = bp -> s.ptr;
p -> s.size += bp -> s.size;
}
else
p -> s.ptr = bp;
freep = p;
}
8-8:
编写函数bfree(p, n),释放一个包含n个字符的任意块,并将它放入由malloc和free维护的空闲块链表中。通过使用bfree,用户可以在任意时刻项空闲块链表中添加一个静态或外部数组
/*bfree函数和moreroce函数并没有太大区别,只是缺少向系统请求空间的步骤*/
#include
#include
#define NALLOC 1024
typedef double Align;
union header {
struct {
union header *ptr;
unsigned size;
} s;
Align x;
};
typedef union header Header;
static Header base;
static Header *freep = NULL;
void *malloc(unsigned nbytes);
static Header *moreroce(unsigned nu);
void free(void *ap);
void bfree(void p, unsigned n);
int main(void)
{
int *p;
int i;
if((p = (int *) malloc(100 * sizeof(int))) == NULL)
return -1;
for(i = 0; i < 100; i++)
*(p + i) = (i + 1) * 2;
for(i = 0; i < 100; i++)
{
printf("%d", *(p + i));
if(i % 10 == 9)
putchar('\n');
}
free(p);
return 0;
}
void *malloc(unsigned nbytes)
{
Header *p, *prevp;
Header *moreroce(unsigned);
unsigned nunits;
if(nbytes <= 0)//malloc函数错误判断
{
fprintf(stderr, "error: Request space size must be a positive integer\n");
return NULL;
}
nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1;
if((prevp = freep) == NULL)
{
base.s.ptr = prevp = freep = &base;
base.s.size = 0;
}
for(p = prevp -> s.ptr; ; prevp = p, p = p -> s.ptr)
{
if(p -> s.size >= nunits)
{
if(p -> s.size == nunits)
prevp -> s.ptr = p -> s.ptr;
else
{
p -> s.size -= nunits;
p += p -> s.size;
p -> s.size = nunits;
}
freep = prevp;
return (void *)(p + 1);
}
if(p == freep)
if((p = moreroce(nunits)) == NULL)
return NULL;
}
}
static Header *moreroce(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if(nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if(cp == (char *) -1)
return NULL;
up = (Header *) cp;
up -> s.size = nu;
free((void *) (up +1));
return freep;
}
void free(void *ap)
{
Header *bp, *p;
bp = (Header *) ap - 1;
if(ap == NULL || bp -> s.size <= 0)//free函数错误判断
{
fprintf(stderr, "error: pointer is empty\n");
return;
}
for(p = freep; !(bp > p && bp < p -> s.ptr); p = p -> s.ptr)
if(p >= p -> s.ptr && (bp > p || bp < p -> s.ptr))
break;
if(bp + bp -> s.size == p -> s.ptr)
{
bp -> s.ptr = p -> s.ptr -> s.ptr;
bp -> s.size += p -> s.ptr -> s.size;
}
else
bp -> s.ptr = p -> s.ptr;
if(p + p -> s.size == bp)
{
p -> s.ptr = bp -> s.ptr;
p -> s.size += bp -> s.size;
}
else
p -> s.ptr = bp;
freep = p;
}
void bfree(void p, unsigned n)
{
Header *up;
unsigned nunits;
nunits = (n - sizeof(Header)) / sizeof(Header);//预留出一个头部大小的空间,其余部分作为空闲空间
if(p == NULL || n <= 0)
{
fprintf(stderr, "error: pointer is empty\n");
return;
}
up = (Header *)p;
up -> s.size = units;
free((void *) (up + 1));//调用free函数将空闲空间插入空闲区域
}