- 本节包含了对合适的内存分配机制的选择,以及常见的存储器操作
选择一个合适的内存分配机制
- 前面学习了很多内存分配的方式,可能会使我们不知道在一个具体问题中不知道该选择哪一种。(大部分情况下malloc( )总是最好的选择,然而在某些情况下,采用其它的方式会更好一些).
静态分配 和 自动分配
有两类是不需要程序员去控制内存分配的:
- 在栈中分配临时变量
- 在堆中分配全局变量
需要程序员控制分配和释放的内存分配方法
- malloc( )
- 优点:简单,方便,最常用
- 缺点:返回的内存并非用0进行初始化了,只是一段不确定内容的空间,程序员如果想当然的以为是用全0初始化的,则会出问题
- 用法: char *p = malloc(size);
- calloc( )
- 优点:使数组分配变得容易,用0初始化了内存
- 缺点:在分配非数组空间时显得较复杂
- 用法:int * y = calloc(50, sizeof(int));
- realloc( )
- 优点:调整已分配的空间大小
- 缺点:只能用来调整已分配空间的大小
- 用法:r = realloc(p, sizeof(struct map));
- brk( ) 和 sbrk( )
- 优点:允许对堆进行深入控制
- 缺点:对大多数使用者来说过于底层
- 用法:brk( )会设置数据段的中断点,sbrk( )将数据段末端增加或减少n个字节,其中sbrk(0)返回的是现在断点的地址.
- 匿名内存映射
- 优点:使用简单,可共享,允许开发者调整保护等级并提供建议,适合大空间的分配
- 缺点:不适合小分配。最优时malloc( )会自动使用匿名内存映射(default情况下,128KB是临界点,临界点可以通过mallopt()来调整在这个临界点)
- 用法:void p = mmap(NULL, 5121024, PROT_READ|PROT_WRITE, MAP_ANONYMOUS| MAP_PRIVATE, -1, 0);
- posix_memalign( )
- 优点:分配的内存按照任何合理的大小进行对齐
- 缺点:相对较新,因此可移植性是一个问题;对于对齐的要求不是很迫切的时候,则没有必要使用
- 用法:
/* 分配1KB,以256字节对齐 */
char *buf;
int ret = posix_memalign(&buf, 256, 1024);
- memalign( ) 和 valloc( )
- 优点:相比posix_memalign( )在其它的Unix系统上更常见
- 缺点:不是POSIX标准,对齐的控制能力不如posix_memalign( )
- alloca( )
- 优点:最快的分配方式,不需要知道确切的大小,对于小的分配非常适合
- 缺点:不能返回错误信息,不适合大分配,在一些Unix系统上表现不好
- 用法:用法与malloc( )一样,但不用自己释放空间. char *p = alloca(1024);
- 变长数组VLAs
- 优点:与alloca( )类似,但在退出此层循环时释放空间,而不是函数返回时
- 缺点:只能用来分配数组,在一些情况下alloca( )的释放方式更加适用,在其它Unix系统中没有alloca( )常见
- 用法:
for (i = 0; i < n; ++i)
char foo[i + 1];
- 以上就是对内存分配几种方法的总结,总的来说,malloc( )仍然是最简单最直观的方法,然而有些内存方法,例如基于栈的分配,在linux下能有很好的性能;不过同时,考虑到可移植性问题,要谨慎运用
^ ^
存储器操作
C语言提供了很多函数进行内存操作。这些函数的功能和字符串操作函数(如strcmp( ) 以及 strcpy( ))类似,但是他们处理的对象是用户提供的内存区域而不是以NULL结尾的字符串。
- 注意:这些函数都不会返回错误信息。
- 字节设置 memset( ):
#include
void *memset(void *s, int c, size_t n);
- 调用memset( )将把从s指向区域开始的n个字节设置为c
- 它经常被用来将一块内存清零:
/* zero out [s, s+256) */
memset(s, '\0', 256);
- 注:如果你可以使用calloc( )分配内存,那就坚决不要使用memset( )了。因为calloc( )可直接从内存中获取已经清零了的内存,这显然比手工的将每个字节清零要高效。
2.字节比较 memcmp( ):
#include
int memcmp(const void *s1, const void *s2, size_t n);
和strcmp( )相似,memcmp( )比较两块内存是否相等。
- 调用memcmp( )比较s1和s2的头n个字节,如果两块内存相同就返回0,如果s1小于s2就返回一个小于0的数,反之则返回大于0的数。
- 注意:因为结构体填充的存在,通过memcmp( )来比较两个结构是否相等是不可靠的。如果要比较两个结构体,只能一个个比较结构体中的每一个元素。
- 字节移动
3.1 memmove( ):
#include
void *memmove(void *dst, void *src, size_t n);
memmove( )复制src的前n字节到dst,返回dst。
- memmove( )可以安全地处理内存区域重叠问题(就是说,dst的一部分在src里面),例如它们允许内存块在一个给定的区域内向上或下移动。
3.1 不支持内存区域覆盖的memcpy( ):
#include
void *memcpy(void *dst, const void *src, size_t n);
- 除了dst和src间不能重叠,这个函数基本和memmove( )一样。如果重叠了,函数的结果是未被定义的。
3.2 memccpy( ):
- 一个安全的复制函数
#include
void *memccpy(void *dst, const void *src, int c, size_t n);
memccpy( )和memcpy( )类似,但当它发现字节c在src指向的前n个字节中时会停止拷贝。它返回指向dst中c后一个字节的指针,或者当没有找到c时返回NULL。
3.3 mempcpy( ):
- 可以使用mempcpy( )来跨过拷贝的内存
#define _GNU_SOURCE
#include
void *mempcpy(void *dst, const void *src, size_t n);
- mempcpy( )和memcpy( )几乎一样,区别在于mempcpy( )返回的是指向被复制的内存的最后一个字节的下一个字节的指针。
- 当在内存中有连续的一系列数据需要拷贝时它是很有用的,但是它并没有太大的性能提升,因为返回的指针只是dst+n而已。
- 这个函数是GNU中特有的
- 字节搜索
4.1 memchr( ):
#include
void *memchr(const void *s, int c, size_t n);
- memchr( )从s指向的区域开的n个字节中寻找c,c将被转换为unsigned char.
- 函数返回指向第一个匹配c的字节的指针,如果没找到c则返回NULL.
4.2 memrchr( ):
#define _GNU_SOURCE
#include
void *memrchr(const void *s, int c, size_t n);
- memrchr( )与memchr( )类似,不过它是从s指向的内存开始反向搜索n个字节,多的字母r代表reverse的意思
- memrchr( )是GNU的扩展函数,而不是C语言的一部分
4.3 memmem( ):
#define _GNU_SOURCE
#include
void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen);
- memmem( )函数在指向长为haystacklen的内存块haystack中查找,并返回第一块和长为needlelen的needle匹配的子块的指针。如果找不到,会返回NULL。
- 这个函数同样是GNU的扩展函数.
- 字节加密
Linux的C库提供了进行简单数据加密的接口:
#define _GNU_SOURCE
#include
void *memfrob(void *s, size_t n);
- memfrob( )函数将s指向的位置开始的n个字节,每个都与42进行异或操作来对数据进行加密。函数返回s。
- 再次对相同的区域调用memfrob( )可以将其转换回来
- 用法:memfrob(secret, len);
- 这个函数用于对数据加密绝对不适合!它仅限于对于字符串的简单处理.
- 它是GNU标准函数.