给变量分配内存空间可分为静态内存分配和动态内存分配。
静态内存分配属于编译时给变量分配的空间,动态分配属于在程序运行时给变量分配的空间
静态分配属于栈分配,动态分配属于堆分配
运行效率上,静态内存比动态内存要快
int a[10] 属于静态分配
int a[n] 或 inta; a = (int)malloc(sizeof(int)*n) 属于动态分配
一般情况下采用malloc()函数进行动态空间分配,并且用free()函数来释放空间
在C中我们开辟内存空间有两种方式 :
1.静态开辟内存 : 例如:
int a;int b[10];
这种开辟内存空间的特点是
所开辟的内存是在栈中开辟的固定大小的 ,如a是4字节 ,数组b是40字节 ,并且数组在申明时必须指定其长度 , 因为数组的内存是在编译时分配好的 . 如果我们想在程序运行时才确定一个数组的大小 ,静态开辟内存空间的方法是不行的 , 举个例子 :
int n;scanf("%d", &n);int a[n];
这样编写会在编译时出错 , 编译器会提醒[ ]中应为常量表达式 , 在C中定义数组时可以用的有以下几种 ,例:
#define N 10enum NUM{M=10};int a1[N];int a2[10];int a3[M];
需要注意的是 ,C中const int n =10 ; n并不能作为数组长度定义数组 , 但C++中则可以 ,
但我们对于开辟空间的需求 , 往往不限于此 , 最常见的定义数组时数组大小在程序运行时才知道的 , 静态开辟就已经无能为力 . 当然有静态开辟 ,肯定也有动态开辟 ,接下来我们就来看动态开辟内存空间
2.动态开辟内存 :
在C中动态开辟空间需要用到三个函数 :
malloc(), calloc(), realloc() ,这三个函数都是向堆中申请的内存空间.
在堆中申请的内存空间不会像在栈中存储的局部变量一样 ,函数调用完会自动释放内存 , 需要我们手动释放 ,就需要free()函数来完成.
下面让我们来看看这几个函数各自的特点, 用法, 区别, 联系。
1.malloc()
void * malloc(size_t size)
1).malloc()函数会向堆中申请一片连续的可用内存空间
2).若申请成功 ,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用malloc()函数开辟动态内存之后, 一定要判断函数返回值是否为NULL.
3).返回值的类型为void型, malloc()函数并不知道连续开辟的size个字节是存储什么类型数据的 ,所以需要我们自行决定 ,方法是在malloc()前加强制转 ,转化成我们所需类型 ,如: (int)malloc(sizeof(int)*n).
4).如果size为0, 此行为是未定义的, 会发生未知错误, 取决于编译器
例
int *p = NULL;int n = 0;scanf("%d", &n);p = (int*)malloc(sizeof(int) * n);if(p != NULL){//....需要进行的操作}
2.free()
void free(void* ptr)
在堆中申请的内存空间不会像在栈中存储的局部变量一样 ,函数调用完会自动释放内存 , 如果我们不手动释放, 直到程序运行结束才会释放, 这样就可能会造成内存泄漏, 即堆中这片内存中的数据已经不再使用, 但它一直占着这片空间, (通俗说就是就是占着茅坑不拉屎), 所以当我们申请的动态内存不再使用时 ,一定要及时释放 .
1).如果ptr没有指向使用动态内存分配函数分配的内存空间,则会导致未定义的行为。
2).如果ptr是空指针,则该函数不执行任何操作。
3).此函数不会更改ptr本身的值,因此它仍指向相同(现在已经无效)的位置(内存)
4).在free()函数之后需要将ptr再置空 ,即ptr = NULL;如果不将ptr置空的话 ,后面程序如果再通过ptr会访问到已经释放过无效的或者已经被回收再利用的内存, 为保证程序的健壮性, 一般我们都要写ptr = NULL; .
注意 : free()不能重复释放一块内存, 如:
free(ptr);free(ptr);
是错的, 已经释放过的内存不能重复释放, 会出现内存错误 .
free()具体用法, 例子 :
int *p = NULL;int n = 0;scanf("%d", &n);p = (int*)malloc(sizeof(int) * n);if(p != NULL){//....需要进行的操作}//操作完成 ,不再使用这片内存空间free(p);p = NULL;
3.calloc()
void * calloc(size_t num,size_t size)
与malloc()函数的区别只在于, calloc()函数会在返回地址之前将所申请的内存空间中的每个字节都初始化为0 .
1).calloc()函数功能是动态分配num个大小(字节长度)为size的内存空间 .
2).若申请成功 ,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用calloc()函数开辟动态内存之后, 一定要判断函数返回值是否为NULL.
3).返回值的类型为void型, calloc()函数虽然分配num个size大小的内存空间 ,但还是不知道存储的什么类型数据 ,所以需要我们自行决定 ,方法是在calloc()前加强制转 ,转化成我们所需类型 ,如: (int)calloc(num, sizeof(int)).
4).如果size与num有一个或都为0, 此行为是未定义的, 会发生未知错误, 取决于编译器
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成这个需求。
例如 :
#include <stdio.h>#include <stdlib.h>int main(){int i, n;int *a;printf("要输入的元素个数:");scanf("%d",&n);a = (int*)calloc(n, sizeof(int));printf("输入 %d 个数字:\n",n);for( i=0 ; i < n ; i++ ) {scanf("%d",&a[i]);}printf("输入的数字为:");for( i=0 ; i < n ; i++ ) {printf("%d ",a[i]);}free (a); // 释放内存return(0);}
要输入的元素个数:3
输入 3 个数字:
22
55
14
输入的数字为:22 55 14
4.realloc()
void * realloc(void * ptr,size_t size)
realloc()函数让动态内存管理更加灵活 .在程序运行过程中动态分配内存大小, 如果分配的太大 ,则浪费空间, 如果太小, 可能还是会出现不够用的情况 .为了合理的利用内存,我们一定会对内存的大小做灵活的调整。那realloc() 函数就可以做到对动态开辟内存大小的调整(既可以往大调整, 也可以往小了调整) .
1).ptr为需要调整的内存地址
2).size为调整后需要的大小(字节数)
3).若调整成功, 返回值为调整大小后内存的起始位置(也就是指向调整后内存的指针), 若失败(当没有内存可以分配时, 一般不会出现), 则返回NULL, 所以还是要对返回值判空
4).如果ptr是空指针, 则和calloc()函数一样作用一样
注意 : realloc()函数在扩大内存空间时有两种情况
1).ptr所指的内存后有足够的内存空间用来扩展 ,如图 :
2).ptr所指内存后没有足够的空间来扩展 ,如图 :
当第二种情况时, 若申请新的内存空间成功, 会将ptr所指向的内存中的内容拷贝到新的内存空间中, ptr所指向的内存会被释放, 返回新得内存地址, 若不成功 ,ptr 所指内存不会被释放, 函数返回NULL
5.realloc()
头文件 #include<malloc.h>void* _cdecl alloca(size_t);参数是申请分配内存的字节数,返回值为分配到的内存地址。
alloca主要的特征是,它是在栈上开辟的空间,当它作用域结束时会自动释放内存,不用像malloc那样,要用free动态释放空间。还有就是malloc开辟空间成功后,并未对内存空间初始化,必须调用memset来进行初始化,而alloca则初始化开辟的内存空间为0.
优点:
这个函数的优点其实很明显:
1、有了这个函数,如何定义某个不确定大小的局部变量就变成了简单的问题。其实关于这点,使用malloc函数也可实现,只不过需要程序员自己释放不需要使用的内存,否则就内存泄露啦_
2、程序员只需要分配空间,释放空间的事情由alloca函数提供的机制来完成
缺点:
1、如果alloca函数导致栈溢出,程序的行为就undefined了,就是不确定、不可控了,很危险的一个bug
2、可移植性很差,对编译器和平台有很强的依赖性,在很多系统上的实现是有bug的,所以并不鼓励使用。32v、pwb、pwb.2、3bsd、4bsd支持这个函数的实现,Linux使用的是GUN版本,在POSIX和SUSv3系统上是不支持该函数的。
虽然alloca函数不被推荐使用,但是在Linux系统上,alloca函数却是一个非常好的用但没有被人们认识到的工具,它表现的异常出色(在各种架构下,通过alloca分配内存就和增加栈指针一样简单),比malloc的性能要好很多,因为它的申请、释放效率很高。对于Linux下较小内存的分配,alloca能收获让人激动的性能。
5.小结
1).malloc()和calloc()函数用法一样, 唯一的区别是calloc()会对所申请内存的每个字节初始化为0
2).malloc(), calloc(), realloc()申请的内存不再使用时 ,一定要用free()释放 ,否则会造成内存泄漏
3).p = realloc(ptr, size)函数返回值不为空时, 释放内存时不需写free(ptr) ,只需写free(p)
参考博文:点击访问
内存管理的基本概念
分析C语言内存的分布先从Linux下可执行的C程序入手。现在有一个简单的C源程序hello.c
#include <stdio.h>#include <stdlib.h>int var1 = 1;int main(void) {int var2 = 2;printf("hello, world!\n");exit(0);}
[tuhooo@localhost leet_code]$ ls -al a.out-rwxrwxr-x. 1 tuhooo tuhooo 8592 Jul 22 20:40 a.out
ls -al命令说明
仔细的读者可能发现了,第一项中最后有些有一个’.’号,这个表示的意思是有这个符号的项单独用ls无法显示,必须使用ls -al才显示。
其中第一项表示文件类型和权限,一共10位
9 8 7 6 5 4 3 2 1 0
r w x r - x r - x
(1)其中第9为表示文件类型,有以下几种:
-: 表示普通文件
d: 目录
b: 块特殊文件
c: 字符特殊文件
l: 符号链接文件
p: 命名管道文件FIFO
s: 套接字文件
(2)8-6位表示所有者权限,5-3位表示同组其它用户权限,2-0:其它用户权限
r:表示可读; w:表示可写;x:表示可执行;
[tuhooo@localhost leet_code]$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=23c58f2cad39d8b15b91f0cc8129055833372afe, not stripped
file命令用来识别文件类型,也可用来辨别一些文件的编码格式。
它是通过查看文件的头部信息来获取文件类型,而不是像Windows通过扩展名来确定文件类型的。
[tuhooo@localhost leet_code]$ size a.out
显示一个目标文件或者链接库文件中的目标文件的各个段的大小,当没有输入文件名时,默认为a.out。
size:支持的目标: elf32-i386 a.out-i386-linux efi-app-ia32 elf32-little elf32-big srec symbolsrec tekhex binary ihex trad-core。
可执行文件在存储(也就是还没有载入到内存中)的时候,分为:代码区、数据区和未初始化数据区3个部分。
C/C++编译的程序占用的内存
再来看一张图,多个一个命令行参数区:
可以看出,此可执行程序在存储时(没有调入到内存)分为代码区(text)、数据区(data)和未初始化数据区(bss)3个部分。
(1)代码区(text segment)
放CPU执行的机器指令(machine instructions)。
(2)全局初始化数据区/静态数据区(initialized data segment/data segment)
该区包含了在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
例如,一个不在任何函数内的声明(全局数据):
int i = 0; /*变量i根据初始值存放到全局初始化数据区*char *str = "abcd" /*abcd存放在文字常量区*/
(3)未初始化数据区(uninitialized data segment),也称BSS区(block started by symbol)
该区存放程序中未初始化的全局变量。
static int i; /*假设声明在函数外*/
一个正在运行着的C编译程序占用的内存分为代码区、初始化数据区、未初始化数据区、堆区和栈区5个部分。
4)栈区
栈又称堆栈,英文statck,由编译器自动分配释放,存放函数的参数值、局部变量的值等(不包括static声明的变量,static意味着在数据段中存放变量)。除此之外,在函数被调用时,栈用来传递参数和返回值。由于栈先进先出的特点,栈特别方便的用来保存和恢复调用现场。
(5)堆区
用于动态内存分配。堆在内存中位于bss区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收。
C程序执行时的内存分配情况示例:
#include <stdio.h>#include <stdlib.h>#include <string.h>int a = 0; /*全局初始化区*/ int b;/*全局未初始化区 BSS*/ char *p1; int main() {int b; /*栈*/char s[] = "abc"; /*栈*/char *p2; /*栈*/ char *p3 = "123456"; /* 123456/0在常量区,p3在栈上*/ static int c = 0; /*全局(静态)初始化区*/p1 = (char *)malloc(10); p2 = (char *)malloc(20); /*分配得来得10和20字节的区域就在堆*/strcpy( p1, "123456"); /*123456/0在常量区,编译器可能会将p1与p3指向的"123456"优化成一个地方*/return 0;}
堆和栈的理论知识
stack和heap的概念
栈,又叫堆栈(stack), 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆,英文叫 heap, 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回 收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。
2.2 stack和heap的差别
(1)申请方式
stack——由程序在执行时系统自动分配。例如:
void fun(){int i; /*声明在函数中一个局部变量i; 系统自动在栈中为i开辟空间 */}
heap——需要程序员自己申请,并指明大小,在c中是malloc函数 。
函数原型是:void *malloc( size_t size )
void fun(){char *p; /*p变量的地址存在在栈中*/p =(char *) malloc( 100 ); /*从堆中申请100个字节的空间,并将该内存强制转换称char*型,首地址给p*/}
(2)申请后系统的响应
栈——只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆——首先操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中那个删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中你给的首地址处记录本次分配的大小,这样,代码中free语句才能正确释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统自动的将多余的那部分重新放入空闲链表中。
(3)申请大小的限制
栈——在windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域,栈顶的地址和栈的最大容量是系统在编译时预先规定好的,如果申请的空间超过栈的剩余空间时,将提示overflow,因此能从栈获得的空间较小。
#include <stdio.h>#include <stdlib.h>int i = 1; /*记录申请的次数*/void fun(){char a[1024*1024] /*一次申请 1M */printf( "NO.%d %ld 字节 地址:%p\n"),i, sizeof(a), a );i++;fun();}int main(){fun();return 0;}
共申请了7M,第8次申请失败,因为栈里面还保存了其他数据,如main函数的参数,所以第8次申请不足1M,运行结果和ulimit查看的信息吻合。
堆——堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址,堆的大小受限于计算机系统的有效虚拟内存,所以堆获得的空间比较灵活,也比较大。
(4)申请的效率
栈——由系统自动分配,速度比较快,但程序员是无法控制的。
堆——由malloc分配的内存,一般速度比较满,而且容易产生内存碎片,但用起来最方便。
(5)栈和堆存储的内容
栈——存储的主函数中函数调用后的下一条指令的地址、局部变量等。
堆——一般在堆的头部用一个字节存放堆的大小,堆的内容由程序员自行安排。
(6)是否产生碎片
对于堆来说,频繁的malloc/free势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来说,则不回存在这个问题。
内存分配方式
内存分配方式有两种:一种是静态分配,一种是动态分配。
静态分配与动态分配的区别
(1) 时间不同
静态分配发生在程序编译和链接的时候,动态分配则发生在程序调入和执行的时候。
(2)空间不同
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由函数malloc进行分配,free函数进行回收。不过栈的动态分配与堆的动态分配不同,它是由编译器进行释放,无需程序员手工实现。
(3)静态对象是有名字的变量,可以直接对其进行操作,而动态对象是没有名字的变量,需要通过指针间接的对它操作。
实例分析
#include<stdio.h>#include<stdlib.h>int a = 0; /*全局初始化区*/ int i; /*全局未初始化区 BSS*/char *p1; /*全局未初始化区 BSS*/void fun(); /*代码段*/#define SHOW_ADR(ID,CATE,I) printf( "变量 %2s 存储在 %2s 中,其地址为: %8x\n", ID, CATE, &I)#define SHOW_POINT_TO(ID,CATE,I) printf( "指针 %2s 存储在 %2s 中,它指向的地址为: %8x\n", ID,CATE, I)#define SHOW_FUN(ID,CATE,I) printf( "函数 %2s 存放在 %2s 中,其地址为: %8x\n", ID,CATE, I )int main() {SHOW_FUN("main","code segment", main);SHOW_FUN("fun","code segment", fun);SHOW_ADR("a","data segment",a);SHOW_ADR("i","BSS",i);SHOW_ADR("p1","BSS",p1);SHOW_POINT_TO("p1","BSS",p1);int b; /*栈区*/SHOW_ADR("b","stack area",b);char s[] = "abc"; /*栈区*/SHOW_ADR("s","stack area",s);char *p2; /*栈区*/SHOW_ADR("p2","stack area",p2);char *p3 = "123456"; /*栈区*/SHOW_ADR("p3","stack area",p3);SHOW_POINT_TO("p3","stack area",p3);static int c =0; /*静态存储区,数据段*/fun();p1 = (char *)malloc(10);/*堆区*/p2 = (char *)malloc(20);printf( "malloc之后 %2s 它指向的堆首地址为: %8x, 尾地址是: %8x\n", "p1", p1, p1+10);printf( "malloc之后 %2s 它指向的堆首地址为: %8x, 尾地址是: %8x\n", "p2", p2, p2+20);free(p1); /*释放堆区内存*/free(p2);return 0;} void fun(){static int d = 0; /*静态存储区,数据段*/d++;SHOW_ADR("d","data segment",d);}
程序运行结果如下:
变量存储位置如下图所示:
资料参考自点击访问