1、经典的C动态内存管理相关函数
标准C提供了malloc, calloc, realloc, free等基于内存堆的管理函数,负责分配可用内存以及释放用过的内存。这些函数本身只负责告诉调用程序,当前有你想要size大小的可用内存块,它的首地址是xxxxx,并不会检查指针的使用是否越过了这个size或者说是offset,这个需要程序员自己去检查。
#include <stdlib.h>
void *malloc(size_t size);
|
malloc的作用分配一块大小为size个字节的可用内存块,并返回首地址。不能分配的时候返回NULL。
#include <stdlib.h>
void calloc(size_t nmemb, size_t size);
|
calloc的作用是分配并初始化内存块,返回一个指向nmemb块数组的指针,每块大小为size个字节。它和malloc的主要不同之处是会初始化(清零)分配到的内存。
#include <stdlib.h>
void *realloc(void *ptr, size_t size);
|
realloc以ptr所指地址为首址,分配size个字节的内存,并返回ptr所指地址。realloc不会初始化分配到的内存块,如果ptr为NULL则相当于malloc,如果size为NULL则相当于free(ptr)。不能分配返回NULL。
#include <stdlib.h>
void free(void *ptr);
|
free清除ptr所指向的地址,它只作清除的工作,并告诉系统,这块地址已经被释放和清除,可以重新被分配。
一个带bug的程序:
/*
* mallocfree.c - try to access illegally an address has wrong allocated
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
char *ptr = malloc(sizeof(char) * 5);
strcpy(ptr, "123456789");
printf("ptr : %s\n", ptr);
printf("freeing ptr\n");
free(ptr);
sleep(1);
printf("ptr : %s\n", ptr);
printf("trying to write the address has freed\n");
strcpy(ptr, "54321");
sleep(1);
printf("ptr : %s\n", ptr);
return 0;
}
|
sleep程序在里面是为了让肉眼更直观的分析程序的运行。执行make mallocfree,生成可执行文件,并运行之:
# ./mallocfree
ptr : 123456789
freeing ptr
ptr :
trying to write the address has freed
ptr : 54321
|
在这里,ptr使用的地址已经越过了malloc分配给它的界限,而且指针在释放以后继续使用,这导致缓冲区溢出的隐患。
另一个程序:
/*
* reallocit.c - realloc a pointer and free it
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *str1 = malloc(sizeof(char) * 4), *str2;
strcpy(str1, "abc");
printf("str1: %s\n", str1);
str2 = realloc(str1, sizeof(char) * 7);
printf ("str2: %s\n", str2);
free(str1);
return 0;
}
|
这个程序先给str1分配一段内存,然后给重新分配这段内存,并分配给str2,最后释放str1所指的内存。注意此时释放的是realloc后的sizeof(char) * 7个字节的空间,而不是原来malloc给str1的sizeof(char) * 4个字节的空间。注意在此代码中,如果再次free(str2),编译运行后程序将报错,因为此地址已被释放;如果在重新分配内存之前继续使用str2指针,可能会造成缓冲区溢出。
上面的函数都是在堆中分配内存,而alloca是在进程栈中获得内存,它的功能也是分配一块未经初始化的内存。
#include <stdlib.h>
void *alloca(size_t size);
|
2、Linux的内存映像管理函数
内存映像的意思是把磁盘文件映像到内存中,以加速I/O操作和便于共享数据,而在共享数据时,为了避免多个进程同时读写数据引起不可预料的后果,通常使用锁或者信号灯等机制实现对共享对象的序列化访问。Linux提供了在sys/mman.h中定义的一系列内存映像管理的函数。
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
|
mmap把打开的磁盘文件fd从offset开始,映像到内存start处,大小为length。成功返回该映像的指针,失败返回-1并设置相应的errno。
映像可选的保护模式prot包括:PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC等。
映像的可选属性flags包括:MAP_FIXED, MAP_PRIVATE, MAP_SHARED, MAP_ANON, MAP_DENYWRITE, MAP_GROWSDOWN, MAP_LOCKED等。其中MAP_PRIVATE或者MAP_SHARED两者必须且只能选择一个,其它都是可选值,用逻辑或添加。MAP_FIXED强制使用start指定的地址,否则执行失败,如果没有使用这个选项,则mmap在start不可用时会尝试把mmap放到其它地方。MAP_LOCKED只用在root权限的进程才能使用,以防止锁定所有可用内存的恶意攻击。
#include <sys/mman.h>
int munmap(void *start, size_t length);
|
munmap解除从start开始大小为length个字节的内存映像并释放内存,如果在munmap之后试图继续访问start,将会产生段错误。在进程终止运行时,所有的内存映像会被自动解除。
#include <sys/mman.h>
int msync(const void *start, size_t length, int flags);
|
msync把从start开始的大小为length个字节的内存映像同步到磁盘,flags包括:MS_ASYNC, MS_SYNC, MS_INVALIDATE。成功返回0,失败返回-1并设置errno。
#include <sys/mman.h>
int mprotect(const void *start, size_t len, int prot);
|
mprotect修改start开始的大小为len个字节的内存映像的保护模式为prot。成功返回0,失败返回-1并设置errno。
#include <sys/mman.h>
int mlock(const void *start, size_t len);
int munlock(void *start, size_t len);
int mlockall(int flags);
int munlockall(void);
|
以上函数对指定的内存映像加锁和解锁,其中mlockall的flags包括MCL_CURRENT和MCL_FUTURE。只有root权限才能使用它们。
#include <sys/mman.h>
void *mremap(void *old_addr, size_t old_len, size_t new_len, unsigned long flags);
|
mremap用指定的flags把地址在old_addr的内存映像大小从old_len调整为new_len,flags如果为MREMAP_MAYMOVE则调整此内存映像的地址。成功返回新地址,失败返回NULL。
阅读(2826) | 评论(0) | 转发(0) |