C语言的学习难点在于内存管理和指针,这篇文章主题在内存管理,主要是从网上找资料看,加上自己的一些理解,贴出来,一是自己方便,二是希望可以得到大家的指教。
首先,先看一下manpage怎么说,细看的话,会解开很多疑惑:
NAME
calloc, malloc, free, realloc - Allocate and free dynamic memory(这几个主要是动态内存分配相关的)
SYNOPSIS
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t size);
DESCRIPTION
calloc() allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated
memory. The memory is set to zero. If nmemb or size is 0, then calloc() returns either NULL, or a unique pointer
value that can later be successfully passed to free().
malloc() allocates size bytes and returns a pointer to the allocated memory. The memory is not cleared. If size
is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
free() frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), cal‐
loc() or realloc(). Otherwise, or if free(ptr) has already been called before, undefined behavior occurs. If ptr
is NULL, no operation is performed.
realloc() changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged to
the minimum of the old and new sizes; newly allocated memory will be uninitialized. If ptr is NULL, then the call
is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call
is equivalent to free(ptr). Unless ptr is NULL, it must have been returned by an earlier call to malloc(), cal‐
loc() or realloc(). If the area pointed to was moved, a free(ptr) is done.
RETURN VALUE
For calloc() and malloc(), return a pointer to the allocated memory, which is suitably aligned for any kind of
variable. On error, these functions return NULL. NULL may also be returned by a successful call to malloc() with
a size of zero, or by a successful call to calloc() with nmemb or size equal to zero.
free() returns no value.
realloc() returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and
may be different from ptr, or NULL if the request fails. If size was equal to 0, either NULL or a pointer suitable
to be passed to free() is returned. If realloc() fails the original block is left untouched; it is not freed or
moved.
CONFORMING TO
C89, C99.
NOTES
Normally, malloc() allocates memory from the heap, and adjusts the size of the heap as required, using sbrk(2).
When allocating blocks of memory larger than MMAP_THRESHOLD bytes, the glibc malloc() implementation allocates the
memory as a private anonymous mapping using mmap(2). MMAP_THRESHOLD is 128 kB by default, but is adjustable using
mallopt(3). Allocations performed using mmap(2) are unaffected by the RLIMIT_DATA resource limit (see getr‐
limit(2)).
The Unix98 standard requires malloc(), calloc(), and realloc() to set errno to ENOMEM upon failure. Glibc assumes
that this is done (and the glibc versions of these routines do this); if you use a private malloc implementation
that does not set errno, then certain library routines may fail without having a reason in errno.
Crashes in malloc(), calloc(), realloc(), or free() are almost always related to heap corruption, such as overflow‐
ing an allocated chunk or freeing the same pointer twice.
Recent versions of Linux libc (later than 5.4.23) and glibc (2.x) include a malloc() implementation which is tun‐
able via environment variables. When MALLOC_CHECK_ is set, a special (less efficient) implementation is used which
is designed to be tolerant against simple errors, such as double calls of free() with the same argument, or over‐
runs of a single byte (off-by-one bugs). Not all such errors can be protected against, however, and memory leaks
can result. If MALLOC_CHECK_ is set to 0, any detected heap corruption is silently ignored; if set to 1, a diag‐
nostic message is printed on stderr; if set to 2, abort(3) is called immediately; if set to 3, a diagnostic message
is printed on stderr and the program is aborted. Using a nonzero MALLOC_CHECK_ value can be useful because other‐
wise a crash may happen much later, and the true cause for the problem is then very hard to track down.
BUGS
By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL
there is no guarantee that the memory really is available. (malloc成功之后,你的内存并不是只有你能用,如果别的进程在你没有使用之前使用了,那你的程序就会有问题了,这个时候你使用memset的话会报Segmentation fault) This is a really bad bug. In case it
turns out that the system is out of memory, one or more processes will be killed by the infamous OOM killer.
In case Linux is employed under circumstances where it would be less desirable to suddenly lose some randomly picked
processes, and moreover the kernel version is sufficiently recent, one can switch off this overcommitting behavior
using a command like:
# echo 2 > /proc/sys/vm/overcommit_memory
See also the kernel Documentation directory, files vm/overcommit-accounting and sysctl/vm.txt.
SEE ALSO
brk(2), mmap(2), alloca(3), posix_memalign(3)
COLOPHON
This page is part of release 3.25 of the Linux man-pages project. A description of the project, and information
about reporting bugs, can be found at http://www.kernel.org/doc/man-pages/.
GNU 2009-01-13 MALLOC(3)
内存分配三种方式 :
(1)从静态存储区域分配。最大的好处就是快。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
考虑数组,数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。对于全局或静态数组,如果已经初始化,并且不是全为0,则放在data段,否则放在bss段。局部数组放在栈。在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在data 段中,未初始化的全局变量保存在bss 段中。深入理解计算机系统这本书应该好好看看呀。
网上看到一种说法,数组名对应着而不指向一块内存。我觉得这有点不妥。数组名也是指针,其类型是TYPE *const,是一种只读指针,不能把它指向其它地方。当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。譬如:
int a[10];
int *p=a;
a分配10*sizeof(int)字节的内存,而p只分配sizeof(int *)字节的内存。
其实,a必然指向一块已分配的内存。但p在赋值前,什么也不指向。定义时,编译器为数组a分配好所有所需的内存,而指针就不用。
编译器实际上会为数组名和数组元素都分配内存,只不过为数组名分配是在后台完成的。
(这个时候,我的老本又发作了,又自动关机了,天暖了,老爱跟我玩这个不好玩的游戏,幸好我及时保存)
和内存分配相关的常见的错误及其对策
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。
常见的内存错误及其对策如下:
内存分配未成功,却使用了它。
编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc来申请内存,应该用if(p==NULL) 或if(p!=NULL) 进行防错处理。
内存分配虽然成功,但是尚未初始化就引用它。
犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。
内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
内存分配成功并且已经初始化,但操作越过了内存的边界。
例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。
忘记了释放内存,造成内存泄露。
含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。
动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误。
释放了内存却继续使用它。
有三种情况:
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
(2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
(3)使用free释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
推荐的一些规则 :
【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
【规则4】动态内存的申请与释放必须配对,防止内存泄漏。
【规则5】用free释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
参考这里 。
以上后面的一些总结,主要来自网上,总结的不错。不过相对于这文章题目来说,还是有点表面,期待进一步学习。