printf函数分析

printf简单实现

  • 第一个参数必须的,是一个”格式字符串“的作用,作用:说明后续的其他参数该怎么解析

  • 格式化字符串标志的前导字符是**%,%后面的字母序列**用来说明后续参数格式

  • 格式化字符串之后的参数,都是按64位8字节、32位4字节对齐。如果不对齐就没办法解析出来那个字符的类型

    • 要不然printf()没办法知道主调函数(caller)怎么传参的
  • 在printf内部,需要调用write()函数,还有vsnprintf()函数v表示可变参数,s表示字符串, n表示记录了最大长度的printf

  • 真正的实现可变参数的是以vs开头一堆函数,v表示可变参数,s表示字符串

  • printf()实现的时候,调用vsnprintf()把参数序列化到一个字符缓冲区

  • 调用write()系统调用,把处理之后的字符序列,打印到“标准输出stdout”

  • Linux上标准输入输出都是一个文件

1
例如:在32位机中,使用%c打印一个字符a,这个字符a也是占用32位的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int scf_printf(const char*fmt,...)
{
va_list ap; //C语言内嵌的“可变参数类型”,'variable',C语言内嵌的一种数据类型
//C语言中有一个va_list的一个宏,用来实现可变参数--可变参数列表
char buf[1024];//打印缓冲区

va_start(ap,fmt);
//va_start和va类似,开头用va_start
//将“格式化字符串”跟“可变参数的变量”关联起来
int ret = scf_vsnprintf(buf,sizeof(buf)-1,fmt,&ap);
//中间调用的是vsnprintf
//vsnprintf函数第一个参数是缓冲区指针,第二个参数是缓冲区长度,第三个参数是格式化字符串,第四个参数是可变参数类型的变量指针
va_end(ap);//结尾是用va_end

if(ret > 0)
{
ret = write(1,buf,ret)// 调用write()系统调用,打印到终端
//STDIN 标准输入 0
//STDOUT 标准输出 1
//STDERR 标准错误输出2
}
return ret;
}

int main()
{
scf_printf("hello %s,%d\n","world",123);
}

vsnprintf函数解析

  • 在调用printf()函数的时候,里面会调用vsnprintf()函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
int scfvsnprintf(char *buf, int size, const char* fmt,va_list* ap)
{
int n = 0;
// 格式化字符串 以'\0'结尾
while(*fmt){
if("%" != *fmt){ // 格式字符 前导符号%,没有发现‘%’就是普通字符
buf[n++]=*fmt++;
continue;
}
fmt++;
if("%"==*fmt){//格式符%之后的第一个字符 %%
buf[n++]=*fmt++;
continue;
}

int prefix = 0;
if('#'==*fmt){//"%#x" 0xff 16进制的0x前缀
prefix++;
fmt++;
}

int l = 0;
if('l' == *fmt){//"%lx","%ld",'%lu',长整型64位打印 (注意:这里#号是在l的前面·‘%#lx’)
l++;
fmt++;
}

if('c'==*fmt){// va_arg()宏,从参数里面读取一个数,从ap可变参数列表里面读取参数,类型为32位整数
buf[n++]=va_arg(ap,int32_t);
}
else if('u'==*fmt){//"%u"、"%lu"、"%llu",最终的序列化到字符串缓冲区,是一个字符串
if(l)
scf_ulong2a(buf,&n,size,va_arg(ap,uint64_t));//将一个无符号的长整型转换为一个字符串
else
scf_ulong2a(buf,&n,size,va_arg(ap,uint32_t));

}
else if('d'==*fmt){
if(l)
scf_long2a(buf,&n,size,va_arg(ap,int64_t));
else
scf_long2a(buf,&n,size,va_arg(ap,int32_t))
}

else if('x' == *fmt){// 16进制
if(prefix){
if(l)//打印带前缀0x
scf_hex2a_prefix(buf,&n,size,va_arg(ap,uint64_t));
else //打印不带前缀0x
scf_hex2a(buf,&n,size,va_arg(ap,uint32_t));
}
}

else if('p'==*fmt)// 打印指针
scf_p2a(buf,&n,size,va_arg(ap,uint64_t));

else if('s' == *fmt)// 字符串
scf_str2a(buf,&n,size,va_arg(ap,char*));

else if('f' == *fmt){// 浮点数 double float
if(l)//处理double类型
scf_double2a(buf,&n,size,va_arg(ap,double));
else//处理 float类型
scf_double2a(buf,&n,size,va_arg(ap,float));
}
// 添加更多的else if,以支持更多的格式字符
else
return -1; // 没有支持的格式化字符串
fmt++; // 下一个字符
}
buf[n] = '\0';// 末尾添0
return n;
}

ulong2a函数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int scf_ulong2a(char *buf, int *pn, int size, uint64_t num)
{
int n = *pn;
int i = n; // 记录当前位置
// 把64位无符号整数,转化成字符串 123--> "123"
// 3 == 123 % 10
// 12== 123 / 10
// ‘0’字符0,‘\0’== 0
while(n < size -1){

buf[n++]=num%10 + '0'; //转化为字符型的数字ascii码值

num /= 10;
if(0==num)
break;
} // 个位在前面
// 数字123 -->字符"321"-->字符"123"
*pn = n--;// n --> 下一个位置

// 进行一个顺序的转换,将高位放在前面

while(i < n){
char c = buf[i];
buf[i++] = buf[n];
buf[n--] = c;
}
return *pn;
}

long2a函数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
int scf_long2a(char* buf, int* pn, int size, int64_t num)
{
int n = *pn;

// 有符号数 判断是不是"负数"
if(n < size -1 && num < 0){
buf[n++] = '-';
num = -num;
}

*pn = n;
return scf_ulong2a(buf,pn,size, num);
}

hex2a函数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int scf_hex2a(char* buf, int* pn, int size, uint64_t num)
{
int n = *pn;
int i = n;

while(n < size - 1){
uint8_t x = num % 16; // 取余数

if(x > 9)
buf[n++] = x - 10 + 'a'; // 10-15用a-f代替
else
buf[n++] = x + '0';

num /= 16;
if(0 == num)
break;
}

*pn = n--;

while (i < n){
char c = buf[i];
buf[i++] = buf[n];
buf[n--] = c;
}
return *pn;
}

hex2a_prefix函数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int scf_hex2a_prefix(char* buf, int* pn, int size, uint64_t num)
{
int n = *pn;

if(n < size -1 -2){
buf[n++] = '0';
buf[n++] = 'x';
}

*pn = n;
return n;

return scf_hex2aprefix(buf,pn,size,num);
}

p2a函数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int scf_p2a(cahr* buf,int* pn, int size, const char* str)
{
if(0==num){
int n = *pn;
char* p = "null";

while(n < size - 1 && *p)
buf[n++] = *p++;
*pn = n
return n;
}
return scf_hex2a_prefix(buf, pn, size , num);
}
// 指针NULL 打印null,其他指针按16进制打印

str2a函数解析

1
2
3
4
5
6
7
8
9
int scf_str2a(char* buf, int* pn, int size, const char* str)
{
int n = *pn;

while(n < size -1 && *str)
buf[n++] = *str++;
*pn = n;
return n;
}

double2a函数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int scf_double2a(char* buf, int* pn, int size, double num)
{
// buf 缓冲区的指针
// pn 缓冲区的当前位置(变量),输入输出的变量
// size 缓冲区的总长度,超过他 缓冲区就溢出...
if(*pn < size -1 && num < 0.0){
buf[(*pn)++] = '-';
num = -num;
}

int64_t l = (int64_t)num;// 提取整数
int64_t diff = (int64_t)((num-l) * 1000000);//把小数部分转化为整数

scf_ulong2a(buf, pn, size, l);

if(*pn < size -1)
buf[(*pn)++] = '.';
return scf_ulong2a(buf, pn, size, diff);
}

printf在glibc的源码

  • 以glibc版本2.31为例

  • printf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* Copyright (C) 1991-2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */

#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>

#undef printf

/* Write formatted output to stdout from the format string FORMAT. */
/* VARARGS1 */
int
__printf (const char *format, ...)
{
va_list arg;
int done;

va_start (arg, format);
done = __vfprintf_internal (stdout, format, arg, 0);
va_end (arg);

return done;
}

#undef _IO_printf
ldbl_strong_alias (__printf, printf);
ldbl_strong_alias (__printf, _IO_printf);
  • fprintf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* Copyright (C) 1991-2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */

#include <stdarg.h>
#include <stdio.h>
#include <libioP.h>


/* Write formatted output to STREAM from the format string FORMAT. */
/* VARARGS2 */
int
__fprintf (FILE *stream, const char *format, ...)
{
va_list arg;
int done;

va_start (arg, format);
done = __vfprintf_internal (stream, format, arg, 0);
va_end (arg);

return done;
}
ldbl_hidden_def (__fprintf, fprintf)
ldbl_strong_alias (__fprintf, fprintf)

/* We define the function with the real name here. But deep down in
libio the original function _IO_fprintf is also needed. So make
an alias. */
ldbl_weak_alias (__fprintf, _IO_fprintf)

malloc函数分析

  • malloc是C语言中堆内存的一个库函数
  • 程序运行的进程结构如下图排序
    • 低地址端是代码段和数据段
    • 数据段的后面就是堆段
    • 在高地址这边最顶上是进程的栈空间
    • 在进程的栈空间与堆空间这边有一块空闲区域,可以使栈往下增长,也可以使堆往上增长
    • 对于进程来说,进程这个数据段的末尾,在Linux系统上有一个特别的标记叫:brk。等到程序需要分配堆空间的时候,brk就会往高地址的地方移动。
    • 在操作系统使用brk函数进行系统调用的时候,分配的堆内存空间只能是连续的。当使用brk函数申请的堆内存被释放后,brk就会往低地址的地方移动,相当于brk是一个指针,指向堆顶。也就是在使用brk函数申请堆内存的时候,只能线性的增长或者线性的回收。
image-20240531194958400
  • malloc负责的就是堆内存的管理,使用malloc来分配堆内存,然后使用free来释放堆内存

malloc简单实现

  • 前提说明:这里的数据类型,例如uint8_t等都是基本数据类型的别称
1
2
3
4
5
6
7
8
9
typedef signed char  		int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef signed __INT64 int64_t;

typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __INT64 uint64_t;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 系统调用 brk()/sbrk()
int brk(uint8_t* addr);
uint8_t* sbrk(uintptr_t inc);

int printf(const char* fmt, ...);

uint8_t* memset(uint8_t* dst,int c, uintptr_t n);
uint8_t* memcpy(uint8_t* dst,uint8_t* scr, uintptr_t n);

// min size of block is 64, so low 6 bits can be used for flags
// flags in prev_size;
// Free Last First
// 2 1 0
struct scf_mblock_t
{
uintptr_t magic; //double free
uintptr_t prev_size; //形成一个链表
uintptr_t cur_size; //与第二个形成一个链表
uintptr_t free; //空闲内存的链表
};
scf_mblock_t* scf__mblocks[30]; // 空闲链表的头指针数组
scf_mblock_t* scf__free_blocks = NULL;
uint8_t* scf__last_brk = NULL; // 当前的brk位置,free完的空闲内存块,只有它的末尾 == 最后的brk的时候,才可以使用brk()修改堆的范围
/*
1.
brk()系统调用,它的作用就是通过移动数据段末尾(brk)来分配堆内存,线性的分配
sbrk()实际上是一个库函数,底层是用brk()实现的,大多数情况下认为它是一个系统调用,

2.
malloc/free灵活的管理brk()分配出来的内存空间
分块,有个块的管理结构
3.
free()的时候为了避免出现“内存碎片”,会把相邻的(空闲)内存块合并
4.
malloc()库的实现都是按8字节对齐,有个最小分配size=64
5.
块的管理结构,跟数据部分是”绑定在一起的“.... 风险就是用户代码在内存越界之后,容易损坏mblock管理结构!
c库为了代码运行的快,把它绑到一起!
free(p)
b = rbtree_find(p); O(log N)
b = p - sizeof(scf_mblock_t);O(1)
C++为了避免用户的误free(),智能指针,shared_ptr
C语言,缺乏“安全设计”... 优先考虑的"时间复杂度低"
6.
频繁的“增删改查”,频繁的 申请 + 释放
c语言的 堆内存 导致的BUG最频繁,最难查...
7.
对次序不敏感的一个内存管理结构,malloc() / free()
-------------栈底

-------------栈顶
空闲
-------------brk3_1
堆b3
-------------
b2释放
------------- brk()/mmap()
b1
------------- brk3_0
数据段
代码段
*/

uint8_t* scf__malloc(uintptr_t size)
{
scf_mblock_t* b;
scf_mblock_t* b2;
// 参数size是用户需要的字节数
uintptr_t bytes = (sizeof(scf_mblock_t) + size + 63) >>6<<6; //库函数 库分配的真正字节数
intptr_t nblocks = sizeof(scf__mblocks) / sizeof(scf__mblocks[0]);

uintptr_t rest;
uint8_t* addr = NULL;
uint8_t* p = NULL;
intptr_t i;

for(i = 0; i <nblocks; i++)
{
if((64<<i) < bytes)
continue;

if(scf__mblocks[i]) //空闲的内存块
break;
}
if(i == nblocks){
uintptr_t pahes = (bytes + 4095) >> 12;

p = sbrk(pages << 12); // 内存页 os处理(设置页的权限 都很简单)
if(!p)
return NULL;
scf__last_brk = p + (pages << 12); // brk()系统调用

rest = (pages << 12) - bytes;

b = (scf_mblock_t*)p; // 用户的新分配的内存块的管理结构

b->prev_size = 0x3; //即是第一个快,也是最后一个块
b->cur_size = bytes;
b->magic = 0x10f0;
}
else {
b = scf__mblocks[i];
scf__mblocks[i] = (scf_mblock_t*)b->free;// 从单链表的头部选“空闲块”

// 类型的区别,只对编译器的前端(人)有用

p = (uint8_t*)b;

rest = b->cur_size - bytes;

b->prev_size &= -0x4;
b->magic = 0x10f0;
}

addr = p + sizeof(scf_mblock_t); // malloc() 返回给用户的地址addr

if(0 == rest){
printf("%s(),%d, b:%p, scf__last_brk: %p\n",__func__,__LINE__,b,scf__last_brk);
return addr;
}

// 拆出一块内存之后,如果还有剩余,要把它挂到空闲的链表上

p += bytes;
b2 = (scf_mblock_t*)p;

for(;i >=0; i--){
if(rest >= (64 << i)){
b2->free = (uintptr_t)scf__mblocks[i]; // 挂在 单链表数组 的头部
scf__mblocks[i] = b2;
break;
}
}

b->cur_size = bytes;
b2->cur_size =rest;
b2->prev_size = bytes | 0x4; // b2 空闲
b2->magic = 0xf010;

if(b->prev_size & 0x2){
b->prev_size &= -0x2;
b2->prev_size |= 0x2;
}
/*
----------
b2 64 最后一块
----------
b 64
----------
*/
printf("%s(),%d, b: %p, scf__last_brk: %p\n",__func__,__LINE__, b, scf__last_brk);
return addr;
}

malloc在glibc的源码

__libc_malloc

  • __libc_malloc只是用来简单封装_int_malloc函数。
  • _int_malloc才是申请内存块的核心。
  • 调用该函数时,首先检查是否有钩子函数__malloc_hook,用于用户自定义的堆分配函数。
  • 用户申请的字节一旦进入申请内存函数中就变成了无符号整数
1
2
3
4
5
6
7
8
9
// wapper for int_malloc
// malloc返回的是一个void类型的指针,传入的形参是size类型
void *__libc_malloc(size_t bytes) {
mstate ar_ptr;
void * victim;
// 检查是否有内存分配钩子,如果有,调用钩子并返回.
void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);
if (__builtin_expect(hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS(0));

__int_malloc

sysmalloc

calloc函数分析

calloc函数简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uint8_t* scf__calloc(uintptr_t n, uintptr_t size)
{
scf_mblock_t* b;
uintptr_t bytes;
uint8_t* p;

size *= n;

p = scf__malloc(size);
if(!p)
return NULL;

b = (scf_mblock_t*)(p - sizeof(scf_mblock_t));

bytes = b->cur_size;
bytes -= sizeof(scf_mblock_t);
printf("%s(),%d, calloc, b: %p, bytes: %ld\n", __func__,__LINE__,b,bytes);

memset(p,0,bytes);

return p;
}

realloc函数分析

realloc函数简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
uint8_t* scf__realloc(uint8_t* p,uintptr_t size)
{
scf_mblock_t* b;
scf_mblock_t* b2;
scf_mblock_t* b3;
uintptr_t bytes;
uint8_t* p2;

b = (scf_mblock_t*)(p - sizeof(scf_mblock_t));

bytes = b->cur_size;
bytes -= sizeof(scf_mblock_t);
/* realloc() 看“连续的块的空间”情况
----------------
空闲的 64
----------------
已经分配的
----------------
*/
if(bytes < size)
{
p2 = scf__malloc(size);
if(!p2)
return NULL;

memcpy(p2,p,bytes);
scf__free(p);

printf("%s(), %d, realloc: %p->%p, size: %ld\n",__func__,__LINE__,p,p2,size);
return p2;

size = (sizeof(scf_mblock_t) + 63 + size) >> 6 << 6;
bytes += sizeof(scf_mblock_t);

if(bytes < size + 64)
return p;

b2 = (scf_mblock_t*)((uintptr_t)b + size);

b->cur_size = size;
b2->cur_size = bytes - size;

if(b->prev_size & 0x2){
b->prev_size &= -0x2;
b2->prev_size = size | 0x2;
}
else{
b3 = (scf_mblock_t*)((uintptr_t)b + bytes);

b2->prev_size = size;
b3->prev_size = (bytes - size) | (b3->prev_size & 0x7);
}

b2->magic = 0x10f0;

p2 = (uint8_t*)b2 + sizeof(scf_mblock_t);
scf__free(p2);

printf("%s(), %d, realloc: %p, free b2: %p, size: %ld\n",__func__,__LINE__,p,p2,size);
return p;
}
}

free函数

free函数简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//free()很麻烦,很容易出错,所有的“回退”都很麻烦
/*
1. 合并内存碎片,尽量合并成大块的空闲内存
2. 不能出错,一旦出错 整个内存管理结构 就乱了
3. 关键就在于 free()函数
*/
int scf__free(uint8_t* p)
{
p -= sizeof(scf_mblock_t); // freeO()时 可以o(1)获取管理结构

scf_mblock_t* b = (scf_mblock_t*)p;
scf_mblock_t* b2;
scf_mblock_t* b3;
scf_mblock_t** pb;

uintptr_t bytes = b->cur_size;
intptr_t nblocks = sizeof(scf__mblocks) / sizeof(scf__mblocks[0]);
intptr_t i;

if(b->prev_size & 0x4){// 二次释放
printf("%s(), %d, error: double free: %p\n",__func__,__LINE__,p);
return -1;
}

if(b->prev_size & 0x4){ // 代码越界 heap corruption
printf("%s(), %d, error: corruption free: %p\n",__func__,__LINE__,p);
return -1;
}

b->prev_size |= 0x4;
b->magic = 0xf010;

while(!(b->prev_size & 0x2))
{ // 往后合并
b2 = (scf_mblock_t*)((uintptr_t)b + bytes);

uintptr_t bytes2 = b2->cur_size;

for(i = nblocks - 1; i >= 0; i--){
if(bytes2 >= (64 << i))
break;
}

for(pb = &scf__mblocks[i]; *pb; pb = (scf_mblock_t**)&(*pb)->free){
if(*pb == b2)
break;
}
if(!*pb)
break;
*pb = (scf_mblock_t*)b2->free;

bytes += bytes2; // 相邻的2个空闲块的大小加在一起
b->cur_size = bytes;

if(b2->prev_size & 0x2)
b->prev_size |= 0x2;
else{
b3 = (scf_mblock_t*)((uintptr_t)b2 + bytes2);

b3->prev_size = bytes | (b3->prev_size & 0x7);
}
}
while(!(b->prev_size & 0x1)){ // 往前合并
uintptr_t bytes2 = b->prev_size & ~0x7;

b2 = (scf_mblock_t*)((uintptr_t)b - bytes2);

bytes2 = b2->cur_size;

for(i= nblocks - 1; i >= 0; i--){
if(bytes2 >= (64 << i))
break;
}
for (pb = &scf__mblocks[i]; *pb; pb = (scf_mblock_t**)&(*pb)->free){
if(*pb == b2)
break;
}
if(!*pb)
break;

*pb = (scf_mblock_t*)b2->free;
bytes += bytes2;
b2->prev_size = bytes;

if(b->prev_size & 0x2)
b2->prev_size |= 0x2;
else{
b3 = (scf_mblock_t*)((uintptr_t)b2 + bytes);

b3->prev_size = bytes | (b3->prev_size & 0x7);
}
b = b2;
}
// 伙伴算法 malloc()/kmalloc(),为了避免“内存碎片”
bytes = b->cur_size;

if(0x7 == (b->prev_size) & 0x7){// 空闲 && 第一块 && 最后一块 == 单次 sbrk() 分配出来的
if(scf__last_brk == (uint8_t*)b + bytes){//最后一次sbrk()分配出来的
printf("%s(), %d, b: %p, scf__last_brk: %p\n",__func__,__LINE__,b, scf__last_brk);
scf__last_brk = (uint8_t*)b;
brk((uint8_t*)b);// 空闲内存还给os
/*
--------------------last brk
b2
--------------------brk3_2
b1
--------------------brk3_1
b0
--------------------brk3_0
free(b0);
free(b1);//只能把内存还给 libc的malloc() 管理系统
free(b2);
*/
flag = 1;
}
}
}
else { // 不能移动brk
b->free = (uintptr_t)scf__free_blocks;
scf__free_blocks = b;
printf("%s(), %d, b: %p\n",__func__,__LINE__,b);
}
return 0;
}
for(i = nblocks - 1; i >= 0; i--){
if(bytes >= (64 << i))
break;
}
b->free = (uintptr_t)scf__mblocks[i];
scf__mblocks[i] = b;

printf("%s(), %d, b: %p\n",__func__,__LINE__,b);
return 0;
}