C语言:什么情况下需要python 释放内存存

C语言内存浅谈
操作的内存分配问题与内存对齐问题对于地层程序设计来说是非常重要的,对内存分配的理解直接影响到代码质量、正确率、效率以及程序员对内存使用情况、溢出、泄露等的判断力。而内存对齐是常常被忽略的问题,理解内存对齐原理及方法则有助于帮助程序员判断访问非法内存。一般c/c++程序占用的内存主要分为以下五种:
1.栈区(stack):系统自动分配,由程序自动创建、自动释放。函数参数、局部变量以及返回值等信息都存在其中
2.堆区(heap):使用自由,不需要预先确定大小。多少情况下需要程序员手动申请、释放。如果不释放,程序结束后有操作系统垃圾回收机制收回。例如,s = (char *)malloc(10),
3.静态区/全局区(static):全局变量和静态变量的存储区域。程序结束后由系统释放
4.常量区:用于存放常量的内存区域
5.代码区:存放代码
/*全局变量,全局区/静态区(static)*/
void fun(int f_jubu); /*程序代码区*/
int main(void)/**/
int m_/*栈区(stack)*/
static int m_/*静态变量,全局区/静态区(static)*/
char *m_zifum,*m_zifuc = "hello";/*指针本身位于栈。指向字符串"hello",位于文字常量区*/
void (*pfun)(int); /*栈区(stack)*/
m_zifum = (char *)malloc(sizeof(char)*10);/*指针内容指向分配空间,位于堆区(heap)*/
printf("&quanju
: %x/n",&quanju);
printf("&m_jubu
: %x/n",&m_jubu);
printf("&m_jingtai: %x/n",&m_jingtai);
printf("m_zifuc
: %x/n",m_zifuc);
printf("&m_zifuc
: %x/n",&m_zifuc);
printf("m_zifum
: %x/n",m_zifum);
printf("&m_zifum
: %x/n",&m_zifum);
printf("pfun
: %x/n",pfun);
printf("&pfun
: %x/n",&pfun);
void fun(int f_jubu)
static int f_
printf("&f_jingtai: %x/n",&f_jingtai);
printf("&f_jubu
: %x/n",&f_jubu);/*栈区(stack),但是与主函数中m_jubu位于不同的栈*/
1.申请方式
由系统自动分配。例如,在函数中声明一个局部变量系统自动在栈中为c开辟空间
需要程序员手动申请,并指明大小,在c中,有malloc函数完成。如p1 = (char *)malloc(10)
2.申请后系统的响应
只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
大多数操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free函数才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
3.申请大小的限制
在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大
4.申请效率的比较
由系统自动分配,速度较快。是程序员无法控制的
由程序员手动分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便
5.堆和栈中的存储内容
在函数调用时,第一个进栈的是函数调用语句的下一条可执行语句的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是函数中的下一条指令,程序由该点继续运行
一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排
内存对齐问题
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。
1.内存对齐的原因
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈
2.正确处理字节对齐
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
a.数组:按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了
b.联合:按其包含的长度最大的数据类型对齐
c.结构体:结构体中每个数据类型都要对齐
从结构体的首地址开始向后依次为每个成员寻找第一个满足条件的首地址x,该条件是x % N = 0,并且整个结构的长度必须为各个成员所使用的对齐参数中最大的那个值的最小整数倍,不够就补空字节
3.对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。对齐规则如下:
a.数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值(或默认值)和这个数据成员类型长度中,比较小的那个进行。在上一个对齐后的地方开始寻找能被当前对齐数值整除的地址
b.结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐.主要体现在,最后一个元素对齐后,后面是否填补空字节,如果填补,填补多少.对齐将按照#pragma pack指定的数值(或默认值)和结构(或联合)最大数据成员类型长度中,比较小的那个进行
c.结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员类型长度的时候,这个n值的大小将不产生任何效果
4.有四个概念值:
1.数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。
2.指定对齐值:#pragma pack (value)时的指定对齐值value。
3.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
由于各个平台和编译器的不同,我的使用的gcc version 4.1.2
(Red Hat 4.1.2-52),来讨论编译器对struct数据结构中各个成员如何进行对齐的。例如:
1.struct A {
}; #结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐,所以使用sizeof(strcut A)值为8。
2.struct B {
#假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指 定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为 4,所以只能存放在起始地址为0x7这四个连续的字节空间中,复核0x,且紧靠第一个变量。第三个变量c,自身对齐 值为2,所以有效对齐值也是2,可以存放在0x9这两个字节空间中,符合0x。所以从0x9存
放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0xB 共有12个字节,sizeof(struct B)=12
3.#pragma pack(2) /*指定按2字节对齐*/
struct C {
#pragma pack() /*取消指定对齐,恢复缺省对齐*/
#们使用预编译指令#pragma pack (value)来告诉编译器。第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x3、0x5四个连续 字节中,符合0x。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x7中,符合 0x。所以从0x07共八字节存放的是C的变量。又C的自身对齐值为4,所以 C的有效对齐值为2。又8%2=0,C只占用0x7的八个字节。所以sizeof(struct C)=8
4.#pragma pack (1) /*指定按1字节对齐*/
struct D {
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
#sizeof(struct C)值是7
5.union E{
}; #我想的是union中变量共用内存,应以最长的为准,那就是20。可实际不然E中各变量的默认内存对齐方式,必须以最长的double 8字节对齐,故应该是sizeof(E)=24
1.数组对齐值为:min(数组元素类型,指定对齐长度).但数组中的元素是连续存放,存放时还是按照数组实际的长度.
如char t[9],对齐长度为1,实际占用连续的9byte.然后根据下一个元素的对齐长度决定在下一个元素之前填补多少byte.
2.嵌套的结构体假设
对于B结构体在A中的对齐长度为:min(B结构体的对齐长度,指定的对齐长度).B结构体的对齐长度为:上述2中结构整体对齐规则中的对齐长度.
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'18:28 提问
free()释放了内存为什么还能使用
#include &stdlib.h&
#include &malloc.h&
typedef struct haha {
int main()
haha **yes = malloc(sizeof(haha *)* 2);
haha *one = malloc(sizeof(haha));
haha *two = malloc(sizeof(haha));
one-&a = 1;
one-&b = 100;
two-&a = 2;
two-&b = 200;
free(one);
free(two);
yes[0]-&a= 3;
yes[1]-&a = 4;
为什么最后还句不报错?按理来说释放了内存应该不能使用的
按赞数排序
typedef struct haha {
int main()
haha **yes = malloc(sizeof(haha ) 2);
haha *one = malloc(sizeof(haha));
haha *two = malloc(sizeof(haha));
//------------------打印地址-----------------//
printf("yes=%p\n",yes);
printf("yes[0]=%p,yes[1]=%p\n",yes[0],yes[1]);
printf("one=%p,two=%p\n",one,two);
printf("*one=%p,*two=%p\n",*one,*two);
free(one);
free(two);
//------------------打印地址-----------------//
printf("----------------释放后-------------\n");
printf("yes=%p\n",yes);
printf("yes[0]=%p,yes[1]=%p\n",yes[0],yes[1]);
printf("one=%p,two=%p\n",one,two);
printf("*one=%p,*two=%p\n",*one,*two);
----------------------同志你好,我是CSDN问答机器人小N,奉组织之命为你提供参考答案,编程尚未成功,同志仍需努力!
因为这个内存还是存在你的内存中的,虽然你释放了,但是你还是可以引用的,这中内存其实是无效的,经常被称为“野指针”,你可以去了解一下
因为这个内存还是存在你的内存中的,虽然你释放了,但是你还是可以引用的,这中内存其实是无效的,经常被称为“野指针”,你可以去了解一下
释放后可以使用,但不是保证总是可以,因为free()后代表以后的malloc()可能会将这块内存区域分配出去。
内存摆着那儿,不管你分配不分配,理论上都是可以用的(因为应用程序操作的内存是虚拟内存(也叫虚拟地址空间),因此有些内存的访问规则是有限制的,访问这些内存一旦违规会导致程序发生虚拟内存违规访问而崩溃),只不过释放后的内存区域可以再分配出去,所以不应该使用。
这就像你把钱包扔在垃圾桶,五分钟之后你或许还可以找得着,五小时后可能是空瓶子了
这个需要理解回收机制。
因为语言不同可能c里面不会太关注回收,因为大神都把这当习惯了,新手根本不会去在意:既然程序可以正常运行,那我回收垃圾干嘛?
先对比一下比较广泛的java,java中如果你给想要让回收的对象赋值null,那么程序就会"记住"你这个操作,之后如果你想使用这个对象的方法,编译器会报错,告诉你使用了未初始化的对象,如果编译器被你代码"骗过",那么执行代码时会抛出空指针异常……
看到了吧,这是编译器做的事儿,或许内存中你要用的数据还存在,但编译器知道那是你放弃的东西就禁止你使用。
而c语言作为低级的面相过程语言,只要没有语法错误,是不会管你干了啥事,不会管你读取内容的地址是否合理(当然const类型的内存是读取不到的),你free的空间,只是告诉系统那内存你不需要了,系统可以在需要内存的时候将其分配给另外的程序或者变量,不过即便它已经被分配出去了,你还是可以读内容,只不过内容代表啥意思你不清楚而已(const地址除外)。
这是c语言比较其他语言的优势,可以直接操作内存,但是!如果你不注意,这会导致程序很危险,万一不小心修改了其他程序的数据,那可能会让程序发生混乱的。C语言内存分配与释放 - 简书
C语言内存分配与释放
首先我们来科普一下:
什么是堆?说到堆,又忍不住说到了栈!什么是 栈?1、什么是堆:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。 2、什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
C语言程序编译的内存分配,堆与栈的区别:栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。
堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。再强调一次,记得要释放!
栈区(stack) ://windows下,栈内存分配2M(确定的常数),超出了限制,提示stack overflow错误//编译器自动分配释放,主要存放函数的参数值,局部变量值等;
堆区(heap):程序员手动分配释放,操作系统80%内存
全局区或静态区:存放全局变量和静态变量;程序结束时由系统释放,分为全局初始化区和全局未初始化区;
字符常量区:常量字符串放与此,程序结束时由系统释放;
程序代码区:存放函数体的二进制代码。
栗子:int a=0; //全局初始化区char p1; //全局未初始化区void main(){ //栈 char s[]="bb"; //栈 char p2; //栈 char p3="123"; //其中,“123\0”常量区,p3在栈区 static int c=0; //全局区 p1=(char)malloc(10); //10个字节区域在堆区 strcpy(p1,"123"); //"123\0"在常量区,编译器 可能 会优化为和p3的指向同一块区域 }栈内存void stackFun(){
int a[1024];
//栈内存自动释放}堆内存void heapFun(){
//void 任意类型的指针
int p = malloc(1024
sizeof(int));
free(p);}void main(){
//在堆内存上,分配40M的内存
while (1){
Sleep(1000);
stackFun();
getchar();}创建一个数组,动态指定数组的大小(在程序运行过长中,可以随意的开辟指定大小的内存,以供使用,相当于Java中的集合)*静态内存分配,分配内存大小的是固定,问题:1.很容易超出栈内存的最大值 2.为了防止内存不够用会开辟更多的内存,容易浪费内存
动态内存分配,在程序运行过程中,动态指定需要使用的内存大小,手动释放,释放之后这些内存还可以被重新使用(活水)
函数:calloc() 分配内存空间并初始化calloc() 函数用来动态地分配内存空间并初始化为 0,其原型为:void calloc (size_t num, size_t size);calloc() 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 numsize 个字节长度的内存空间,并且每个字节的值都是0。【返回值】分配成功返回指向该内存的地址,失败则返回 NULL。
函数:malloc() 动态地分配内存空间malloc() 函数用来动态地分配内存空间(如果你不了解动态内存分配,请查看:C语言动态内存分配及变量存储类别),其原型为:void malloc (size_t size);应用在程序中代码如下:void main(){
//静态内存分配创建数组,数组的大小是固定的
//int i = 10;
//int a[i];
printf("输入数组的长度:");
scanf("%d",&len);
//开辟内存,大小len4字节
int p = malloc(len
sizeof(int));
//p是数组的首地址,p就是数组的名称
//给数组元素赋值(使用这一块刚刚开辟出来的内存区域)
int i = 0;
for (; i & len - 1; i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
//手动释放内存
//free()释放动态分配的内存空间
getchar();}
realloc 重新分配内存void main(){
printf("第一次输入数组的长度:");
scanf("%d", &len);
//int p = malloc(len
sizeof(int));
int p = calloc(len, sizeof(int));
int i = 0;
for (; i & i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
printf("输入数组增加的长度:");
scanf("%d", &addLen);
//内存不够用,扩大刚刚分配的内存空间
//1.原来内存的指针 2.内存扩大之后的总大小
int p2 = realloc(p, sizeof(int) * (len + addLen));
if (p2 == NULL){
printf("重新分配失败,世界那么大,容不下我。。。");
新分配内存的两种情况:** //缩小,缩小的那一部分数据会丢失//扩大,(连续的)1.如果当前内存段后面有需要的内存空间,直接扩展这段内存空间,realloc返回原指针2.如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据库释放掉,返回新的内存地址3.如果申请失败,返回NULL,原来的指针仍然有效//接着上面的代码重新赋值
printf("--------------------------\n");
for (; i & len + addL i++){
p2[i] = rand() % 200;
printf("%d,%#x\n", p2[i], &p2[i]);
//手动释放内存
if (p != NULL){
if (p2 != NULL){
p2 = NULL;
getchar();}
内存分配的几个注意细节1.不能多次释放;2.释放完之后(指针仍然有值),给指针置NULL,标志释放完成;3.内存泄露(p重新赋值之后,再free,并没有真正释放内存);
void main(){
printf("输入数组的长度:"); scanf("%d", &len); int p = malloc(len
sizeof(int)); int i = 0; for (; i & i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]); } if (p != NULL){
p = NULL; } getchar();}
以上就是C语言中对内存的分配与释放,常用的几个函数~学习理解并整理下来的笔记。希望大家能够指点或提出宝贵意见,谢谢!一起学习。转载请注明出处: 个人站点:本文链接:
今天看书的时候看到free函数释放动态申请的内存时只需要把内存块的首地址传过去就行了,显然仅仅依靠首地址是无法确定要释放多少内存的,猜想应该在某处存放着这个内存块的大小,网上搜了搜发现在Linux里面glibc在分配内存的时候会在内存块的地址前面的4个字节出存放内存块的大小,就猜想Windows里面应该也是这样。写了一个小程序测试了下:
#include &stdio.h&
#include &STDLIB.H&
int main(void)
int *p = (int *) malloc(10 * sizeof(int));
运行调试,先找到这个内存块的地址:
现在看一下内存:
由于申请了40个字节大小的内存块,转换成16进制就是0x,在内存中就是28 00 00 00 ,看一下上图可以发现内存块首地址之前的16个字节处和猜想中的结果一样,接下来申请一个400字节大小的内存块,转换成16进制就是0x,在内存中就是90 01 00 00,再看一下运行的结果:
依然和推测出的结果一样,可见Windows里面当我们使用free函数释放内存的时候,函数会把内存块的首地址加上16,从这个位置就可以找到内存块的大小。
阅读(...) 评论()}

我要回帖

更多关于 什么情况需要16g内存 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信