随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
1、进程需要的一个重要资源就是内存。而内存又使用虚拟内存分页机制进行管理,每页的大小一般是4K字节。虚拟内存是一个很大的线性虚拟地址空间,可以进行数据交换和保存指令。虚拟内存的物理支持硬件可以是物理内存也可以是磁盘。
2、程序不需要关心内存换页机制的具体操作。操作系统会根据实际需要将程序请求的文件等数据读入内存,或者将程序不需要的数据清除或者移出内存并存入磁盘。对程序来说这些操作都是透明的,根本不知道这些底层的操作。
3、进程的虚拟地址空间被划分为很多部分,称为“段”,主要有如下三个段:
- text:代码段。包含程序的指令、文字量和静态常量。这些都是在被exec系列函数执行时都分配好了内存空间和地址,并且他们的存储空间和地址在从程序执行到程序退出都不会改变。
- data:数据段。存储程序需要的数据。可以由exec系列函数预分配或者预加载,也可以有程序调用函数改变本段的空间大小。但是,最小尺寸是固定的。
- stack:堆栈段。包含程序的堆栈区。会根据需要程序需求而增加空间大小,但是不会减小空间尺寸。
4、与内存动态分配相关的函数:
- void * malloc (size_t size)在“stdlib.h”中定义:创建并返回一个指向“size”指定大小的内存块的指针。如果分配失败,那么就返回“NULL”指针。这个内存块中的内容没有进行初始化,数据是随机的不确定的。需要手动调用“memset”函数进行初始化操作。实际使用中,在为字符串“string”分配内存时,传入参数“size”一定要记住增加一个字节,也就是“size+1”。因为字符串是以“/0”结尾的,需要为这个null字符分配一个空间。
- void * memset (void *block, int c, size_t size)在“string.h”中定义:这个函数将“c”转换为“unsigned char”类型,并从“block”的内存块第一个字节开始,将“size”个字节的内容都设置成“c”的值,并返回赋值后的内存块指针。
- void free (void *ptr)在“stdlib.h”中定义:释放由“ptr”指向的内存块,一般是由“malloc”分配的内存块。不要尝试访问已经由“free”释放的内存块的任何数据,否则将发生内存访问错误。如果有必要请在调用“free”前,先将要释放的内存块数据拷贝一份另存以备后用。
- void * realloc (void *ptr, size_t newsize)在“stdlib.h”中定义:重新设置由“ptr”指向的内存块大小为“newsize”。如果没有空闲的内存空间可用,那么函数将返回一个“NULL”指针。由于“ptr”指向的内存块末尾后面的其他内存空间可能被其他变量等使用;所以,“realloc”将根据需要重新分配一块全新的内存块并将“ptr”指向的内存块内容复制过去,并返回新分配的内存地址指针。如果传入“ptr”参数为“NULL”,那么“realloc”将完成与“malloc”一样的功能。虽然使用方便了,但是有些老的C编译器不支持,将报错。
- void * calloc (size_t count, size_t eltsize)在“stdlib.h”中定义:分配由“count”和“eltsize”乘积也就是“count*eltsize”大小的内存块,并将内存块的内容全部以“0”填充。
5、一般来说,“malloc”分配的内存块地址是以8的倍数对齐的,对于64位系统则是16的倍数对齐。
6、GNU C允许程序员编写钩子(HOOK)函数来修改“malloc”、“realloc”和“free”这三个函数的执行动作。撰写钩子函数有助于调试程序。在实际操作中务必要注意保存和恢复原函数地址,防止错误的递归调用。
可以将自己编写的钩子函数分别赋值给以下变量即可,在“malloc.h”中定义:
- __malloc_initialize_hook:钩子函数原型 static void my_init_hook (void);
- __malloc_hook:钩子函数原型 void *function (size_t size, const void *caller);“caller”参数是在调用“malloc”之前在“stack”栈上的地址。这个参数值允许跟踪程序的内存消耗。
- __realloc_hook:钩子函数原型 void *function (void *ptr, size_t size, const void *caller);
- __free_hook:钩子函数原型 void function (void *ptr, const void *caller);
- __memalign_hook:钩子函数原型 void *function (size_t alignment, size_t size, const void *caller);
7、GNU C还扩展支持一种调试功能。在“mcheck.h”中定义了两个函数:void mtrace (void)和void muntrace (void)。可以用来检测程序是否存在内存溢出等问题。“mtrace”函数将在“malloc”、“realloc”和“free”被调用时向一个指定的文件写入调试信息。
#include <mcheck.h>
int
main (int argc, char *argv[])
{
#ifdef DEBUGGING
mtrace ();
#endif
...
}
8、“Obstacks”是一个包含很多对象的内存池。这里的对象泛指任意的数据类型,与C++里面的面向对象的对象概念定义不一样。程序员可以创建很多独立的“Obstacks”并向其中添加对象。“Obstacks”可以包含很多任意大小的对象,但是这些对象在“Obstacks”中都有边界约束,因此不会混乱。
9、在“obstack.h”头文件中定义了一个“obstack”结构原型:struct obstack;这个结构有一个最小的固定尺寸,里面记录了“obstack”状态和为每个对象分配的内存地址,但是不包含对象自身的任何数据。所以不要尝试通过这个“obstack”结构内容直接访问对象。需要调用下述一些列函数来处理。
10、在“obstack”中的对象被打包成一个很大的内存块,这个块成为“chunks”。“obstack”结构指向一个“chunks”链表。“obstack”是自动管理内存分配和释放的,不需程序员关心内存问题。
11、但是,“obstack”是一个抽象层的应用框架对象,它没有向系统申请和释放内存的能力;因此如果要使用“obstack”,需要为他提供一个内存分配函数和内存释放函数。
具体操作如下:
a、首先在用户程序中定义如下信息:
-
- #define obstack_chunk_alloc xmalloc:将“xmalloc”函数作为“obstack”对象的内存分配函数;
-
#define obstack_chunk_free free:将“ free”函数作为“ obstack”对象的内存释放函数;
并且创建一个错误句柄回调函数,这个函数在obstack分配内存失败时会调用;因此可以通过这个回调函数来对失败时做一些想做
的事情。这个函数没有返回值,但是在函数实现代码中应该调用“exit”或者“longjmp”来终止程序运行或者长跳转至其他目标区
域。
示例:
void my_obstack_alloc_failed (void)
...
obstack_alloc_failed_handler = &my_obstack_alloc_failed;
b、在使用“obstack”前,需要调用“obstack_init”函数初始化一个“obstack”对象;
原型定义:int obstack_init (struct obstack *obstack-ptr);可以有两种初始化obstack的方法。
示例(1):
static struct obstack myobstack;//定义一个静态obstack结构类型变量
...
obstack_init (&myobstack);//初始化这个变量
示例(2):
struct obstack *myobstack_ptr
= (struct obstack *) xmalloc (sizeof (struct obstack));//定义完obstack结构类型变量后,可以手动调用
//xmalloc函数为变量分配内存
obstack_init (myobstack_ptr);//初始化这个变量
有一点需要注意,由于“obstack_init ”函数总是返回“1”,在以前的版本中在内存分配错误是会返回“0”。
12、用于“obstack”操作的系列函数:
a、用于向obstack中分配对象的函数,下列函数在分配内存错误时都会调用obstack_alloc_failed_handler回调函数:
-
- void * obstack_alloc (struct obstack *obstack-ptr, int size):向“obstack-ptr”指定的“obstack”结构中申请分配一个“size”大小的内存空间,但是不对这个内存空间的原始数据进行初始化操作。执行完毕,返回一个指向这个被分配内存空间的地址。
示例:
struct obstack string_obstack;//声明一个obstack结构的变量
char *copystring (char *string)
{
size_t len = strlen (string) + 1;//这里计算将要分配的内存空间大小,由于C语言中字符串的末尾以'/0'结
束,因此取“string”参数字符串长度时没有包含进'/0'字符的占位空间,所以要加“1”。
char *s = (char *) obstack_alloc (&string_obstack, len);//申请分配内存
memcpy (s, string, len);//将输入的字符串复制到string_obstack中申请好的内存空间中。
return s;//返回新分配的内存地址
}
-
- void * obstack_copy (struct obstack *obstack-ptr, void *address, int size):分配“size”大小的内存空间,然后将从“address”地址开始复制“size”个字节到新分配的内存空间。
-
void * obstack_copy0 (struct obstack *obstack-ptr, void *address, int size):功能同上,但是会在完成复制后,在复制数据的尾部附加上' /0'字符。这个附加的' /0'字符不占用“ size”参数指定的大小。
示例:同样完成上面复制字符串的功能
char *obstack_savestring (char *addr, int size)
{
return obstack_copy0 (&myobstack, addr, size);
}
b、释放obstack中对象的函数
-
-
void obstack_free (struct obstack *obstack-ptr, void *object):如果“ object”参数传入了 null空指针,那么将释放“ obstack-ptr”指定的“ obstack”中的所有对象,并且“ obstack-ptr”将指向一个未初始化的“ obstack”对象。如果“ object”参数传入了一个有效的对象地址,那么就只释放这个对象所占用的内存空间。这个对象释放后,先前它占用的内存空间将来可以分配给其他的加入的对象。如果一个“ obstack”中的对象都是放完了,那么它先前占用的内存空间将被回收重新编组到 chunks中,以备后用。
-
13、有一点需要注意,不同的编译器,对操作obstack的接口实现也不同,可能用函数或者宏(macro)实现。如果使用老的编译器,可能将接口用宏实现。那么你可以向函数一样调用这个宏,但是却有很多操作是不允许的。例如,不能取得宏的地址。
另外,调用宏还需要特别注意一点:由于宏代码是内联展开的,所以宏的第一个参数可能会被调用很多次,那么就必须保证这个参数在每次调用时都保证值得一致性。否则,程序运行后可能会发生不可预料的结果。
在ISO标准C中,每个功能都被实现成一个函数和一个宏,根据实际需要可以获取函数的指针,但是一般情况下都使用宏。函数和宏在实际使用中的区别,看示例:
char *x;
void *(*funcp) ();//定义一个函数指针变量
/* 下面代码使用的是宏*/
x = (char *) obstack_alloc (obptr, size);
/* 下面代码使用的是函数,注意“(...)” */
x = (char *) (obstack_alloc) (obptr, size);
/* 取得函数地址,赋给funcp变量 */
funcp = obstack_alloc;
但是,如果使用GNU C编译器的话,就不需要注意这些了。因为GNU C做了语义扩展,允许使用宏时只计算一次第一个参数的值。
14、由于obstack的chunks需要持续使用内存,可能一次加一个对象,也可能添加一个或多个字节。所以就需要obstack是个可增长的对象。程序员不需要关心是obstack内部如何实现增长的。这里提供了一些函数来实现手动增长obstack对象,但是在增长目的完成后,需要明确调用“obstack_finish”函数来说明增长完毕。在增长的过程中对象的地址是不确定的,因为obstack会根据空间需要将对象复制到一个新的chunk中;所以,在增长过程中不要对这个对象进行操作,否则可能造成对象的数据覆盖掉其他的对象数据,造成多个对象间的数据重叠。在程序运行中将造成不可预料的结果。
函数如下:
- void obstack_blank (struct obstack *obstack-ptr, int size):向obstack-ptr添加一块大小为“size”的未初始化内存空间。如果“size”传入了一个负数,那么将会缩小obstack-ptr的内存空间。但是绝对不能传入一个“0”,否则将会产生不可预料的结果。
- void obstack_grow (struct obstack *obstack-ptr, void *data, int size):基本功能与“obstack_copy”函数类似。向obstack-ptr添加一块大小为“size”的内存空间,并将从“data”地址开始“size”个字符拷贝到这个分配的内存中。
- void obstack_grow0 (struct obstack *obstack-ptr, void *data, int size):基本功能与“obstack_copy0”函数类似。向obstack-ptr添加一块大小为“size”的内存空间,并将从“data”地址开始“size”个字符拷贝到这个分配的内存中。最后在拷贝过来的字符序列尾部附加上'/0',这个'/0'所占的“1”个字符空间不包含在“size”中,属于自动追加的。
- void obstack_1grow (struct obstack *obstack-ptr, char c):一次向obstack-ptr添加一个字符'c'。
- void obstack_ptr_grow (struct obstack *obstack-ptr, void *data):向obstack-ptr中添加sizeof(void*)个字节空间。
- void obstack_int_grow (struct obstack *obstack-ptr, int data):向obstack-ptr中添加size(int)个字节空间,并将“data”的值赋给这个添加的字节空间。
- void * obstack_finish (struct obstack *obstack-ptr):调用此函数明确说明obstack-ptr增长完毕。此时返回的才是obstack-ptr真实有效地地址指针,然后就可以进行通常obstack对象的操作了。
15、为了在增长obstack的过程中跟踪当前obstack的内存占用大小,可以调用“obstack_object_size”函数。而且一定要在调用“obstack_finish”函数前调用这个函数,否则本函数将返回“0”。
函数原型:int obstack_object_size (struct obstack *obstack-ptr);
如果想在增长obstack的过程中想撤销增长,那么需要先调用“obstack_finish”函数完成增长,然后立刻调用“obstack_free”函数释放增长的空间,这样obstack就恢复到增长前的状态。示例:obstack_free (obstack_ptr, obstack_finish (obstack_ptr));
16、如果需要频繁的增长obstack,那么就最好调用如下扩展的快速函数。因为上述的增长函数会在添加对象时检查当前obstack的空余空间是否够用,然后再添加对象,这样效率太低了。而下述的快速增长函数则不考虑空间问题,空间是否够用需要程序员在增长前先检查当前obstack的空间是否够用,然后再进行增长操作。
- int obstack_room (struct obstack *obstack-ptr):获取当前obstack-ptr的空余空间大小。
- void obstack_1grow_fast (struct obstack *obstack-ptr, char c)
- void obstack_ptr_grow_fast (struct obstack *obstack-ptr, void *data)
- void obstack_int_grow_fast (struct obstack *obstack-ptr, int data)
- void obstack_blank_fast (struct obstack *obstack-ptr, int size)
示例:
void add_string (struct obstack *obstack, const char *ptr, int len)
{
while (len > 0)
{
int room = obstack_room (obstack);//获取当前obstack空余空间的大小
if (room == 0)
{
/* obstack没有足够的空闲空间了。向obstack添加一个字符,
这样系统就会在分配一个新的有足够空间的chunk。*/
obstack_1grow (obstack, *ptr++);
len--;
}
else
{ /* 如果有足够的空余空间,那么就调用快速函数每次添加一个字符*/
if (room > len)
room = len;
len -= room;
while (room-- > 0)
obstack_1grow_fast (obstack, *ptr++);
}
}
}
还在学习中...