内存管理

stdlib库中有几个内存管理相关的函数

序号 函数和描述
1 void *calloc(int num, int size);
在内存中动态地分配 num 个长度为size 个字节 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
2 void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3 void *malloc(int size);
在堆区分配一块size个字节的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4 void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展到 newsize,newsize的单位还是字节

总之三个alloc分配的都是字节,calloc分配num个size字节的空间,malloc分配size个字节的空间,realloc重新分配size个字节的空间,三个都返回分配的空间的基址,然后用一个指针存这个基址,free()用来释放掉这个指针.

如:

#include<stdio.h>
#include<stdlib.h> int main(){
int *a=calloc(10,4);
realloc(a,80);
for(int i=0;i<20;i++){
*(a+i)=2*i;
}
for(int i=0;i<20;i++){
printf("%d ",*(a+i));
}
free(a);
}

分配10个4字节空间,一个4字节的空间可以存一个int或long,之后重新分配80个字节,也就是之前空间的两倍

两次for循环存数,读数,最后释放掉指针

问题1:

重新分配内存会不会把之前的存储释放?

实验:

#include<stdio.h>
#include<stdlib.h> int main(){
int *a=malloc(4);
*a=78;
printf("%d\n",*a);
realloc(a,8);
*(a+1)=99;
printf("%d %d",*a,*(a+1));
}

结果:

所以重新分配内存不会释放掉之前的内存,而是在原内存的基础上添加新的空间,而且如果重新分配的内存比之前的空间小,并不会截断之间后面的内存,还是可以访问的

#include<stdio.h>
#include<stdlib.h> int main(){
int *a=malloc(8);
*a=78;
*(a+1)=99;
realloc(a,4);
printf("%d %d",*a,*(a+1));
}

问题2:

如果只分配4字节的空间,但是强制使用大于4字节的空间,能正常存取吗?

#include<stdlib.h>
#include<stdio.h> int main(){
int *a=malloc(4);
for(int i=0;i<4;i++){
*(a+i)=i+4;
}
for(int i=0;i<4;i++){
printf("%d ",*a);
a++;
}
}

输出

存取成功!也就是说,实际分配的空间不管多大,当指针移动时,都是按数据的类型移动的,并按数据类型对齐,所以能正确的存取数据

看调试过程,a的地址是每次增加4的

如果不用alloc分配内存,直接用一个指针呢?

#include<stdlib.h>
#include<stdio.h> int main(){
int *a;
a++;
a++;
*a=1;
printf("%d ",*a);
}

断点调试时可以看到a的地址是0x10,每次a++,地址+4

每次a++的反汇编代码是add指令

在对*a赋值时出现Segmentation fault异常,也就是不能对*a赋值

segmentation fault:随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.

查看反汇编的内存地址

可以看到0x3fffff以前的内存是无法访问的,从0x400000才是程序能用的地址

猜测:因为内存的分段:

0x3fffff之前的内存空间都是属于kernal的,所以不能用

那如果给指针初始化一个可使用的内存空间呢?

#include<stdlib.h>
#include<stdio.h> int main(){
int z;
int *a=&z;
*a=1;
printf("%d ",*a);
}

成功,这也是正常的调用

断点调试看内存地址:

问题3:

如果给指针初始化一个int型的地址,它会自动对齐后续的空间吗?

若继续后移指针,并存数取数:

#include<stdlib.h>
#include<stdio.h> int main(){
int z;
int *a=&z;
*a=1;
printf("%d ",*a);
a++;
*a=2;
printf("%d ",*a);
}

结果只输出了一个,但也没有报错,再断点调试看看:

可以看到在后移指针后,在给*a赋值,a的地址发生了改变,也就说*a=a,a指向本身?

验证:

#include<stdlib.h>
#include<stdio.h> int main(){
int z;
int *a=&z;
*a=1;
printf("%d ",*a);
a++;
printf("%d %d",a,*a);
}

尝试*a=&x

正常情况下,如

int *a;
int x;
*a=&x;

这样是错误的,会报异常:assignment to 'int' from 'int *' makes integer from pointer without a cast,即从“int *”赋值到“int”会使指针中的整数没有强制转换,因为a是一个int型指针,*a得到的应该是一个int型,&x则是一个int *,这个操作相当于把一个int *赋值给一个int

但是由上个问题实验得知,初始化后的指针后移会指向本身,所以int是可以存int*的

实践:

#include<stdio.h>
int main(){
int z=1;
int *a=&z;
a++;
int x;
*a=&x;
*a=4;
printf("%d",*a); }

虽然有warning,但是成功输出了,证明了此时a就是指向本身,同时也证明了c语言中的数据类型是互用存储的,只要字节长度相同就能存,int ,int *只是一个规定的标签罢了

运行完*a=&x后,a的值变成了x的地址

问题4:

如果只是在int *前有int型变量初始化,那么指针会自动对齐

如:

#include<stdio.h>
int main(){
int a=1;
int *p;
*p=3;
p++;
*p=5;
}

调试过程:

而且可以看到,指针后移之后,*p被初始化成了0

问题总结

alloc分配内存时会自动对齐,即使分配的空间很小,也能继续使用空间

int a;如果不赋值的化,就会被编译器优化掉

直接对指针操作,无法赋值,因为初始它指向的是不能访问的内核空间

如果给指针赋值&a,并不会自动对齐后续的空间,p++得到的是&a++,实际上,p++后指针指向本身

但是如果给变量赋了初值,int a=1;之后对指针直接操作,是自动对齐的,且,p++后的*p会初始化成0

以上探究只是为了搞清楚内存分配的实质,但在项目中不要随便用,这些可能属于未定义行为,很容易导致异常

C温故补缺(十四):内存管理的更多相关文章

  1. 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)

    使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...

  2. 十.oc内存管理

    引用百度百科图 栈(stack)又名堆栈. 栈定义:栈是限定仅在表头进行插入和删除操作的线性表(有序).(又称:后进先出表) (动态)数据展示存储的地方.(举例:升降电梯)特点:先进后出(FILO—F ...

  3. 《objective-c基础教程》学习笔记(十)—— 内存管理

    本篇博文,将给大家介绍下再Objective-C中如何使用内存管理.一个程序运行的时候,如果不及时的释放没有用的空间内存.那么,程序会越来越臃肿,内存占用量会不断升高.我们在使用的时候,就会感觉很卡, ...

  4. MySQL(十四)管理维护及性能优化

    关于MySQL的学习,<MySQL必知必会>这本书呢,看完已经两个月了,一直被工作以及生活的一些琐事拖着,趁着今晚有空闲,就整理完了最后的几章学习笔记,接下来的学习计划呢, 应该是pyth ...

  5. Linux命令(二十四) 磁盘管理命令(二) mkfs,mount

    一.格式化文件系统 mkfs 当完成硬盘分区以后要进行硬盘的格式化,mkfs系列对应的命令用于将硬盘格式化为指定格式的文件系统.mkfs 本身并不执行建立文件系统的工作,而是去调用相关的程序来执行.例 ...

  6. Oracle学习(十四):管理用户安全性

    --用户(user) SQL> --创建一个名为 grace password是password 的用户,新用户没有不论什么权限 SQL> create user grace identi ...

  7. OC-手动内存管理

    一.为什么要进行内存管理 •移动设备的内存极其有限,每个app所能占用的内存是有限制的 • •下列行为都会增加一个app的内存占用 Ø创建一个OC对象 Ø定义一个变量 Ø调用一个函数或者方法 • •当 ...

  8. 启动期间的内存管理之pagging_init初始化分页机制--Linux内存管理(十四)

    1 今日内容(分页机制初始化) 在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制. 在分页机制完成后, 才会开始初始化系统的内存数据结构(包括内存节点数据和 ...

  9. Spark(四十六):Spark 内存管理之—OFF_HEAP

    存储级别简介 Spark中RDD提供了多种存储级别,除去使用内存,磁盘等,还有一种是OFF_HEAP,称之为 使用JVM堆外内存 https://github.com/apache/spark/blo ...

  10. 【读书笔记】C#高级编程 第十四章 内存管理和指针

    (一)后台内存管理 1.值数据类型 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64 ...

随机推荐

  1. JAVA中让Swagger产出更加符合我们诉求的描述文档,按需决定显示或者隐藏指定内容

    大家好,又见面啦. 在前一篇文档<JAVA中自定义扩展Swagger的能力,自动生成参数取值含义说明,提升开发效率>中,我们探讨了如何通过自定义注解的方式扩展swagger的能力让Swag ...

  2. JS作用域、变量提升和闭包

    作用域 作用域可以理解为JS引擎执行代码的时候,查找变量的规则. 从确定变量访问范围的阶段的角度,可以分为2类,词法作用域和动态作用域.js是词法作用域. 从变量查找的范围的角度,可以分为3类,全局作 ...

  3. Java SE 多态

    1.多态 方法的多态 //方法重载体现多态 A a = new A(); //这里我们传入不同的参数,就会调用不同sum方法 System.out.println(a.sum(10,20)); Sys ...

  4. thinkphp5.1打印SQL语句

    最近在写tp框架搭建的小玩具,有时候我们需要查看SQL语句.所以就诞生了这篇随笔,命令如下: $xxx=db('xxx')->where('x',xx)->select(); $sql=D ...

  5. Compose 命令说明

    命令对象与格式 对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器.如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响. 执行 ...

  6. 《Go 精进之路》 读书笔记 (第一次更新)

    <Go 精进之路> 读书笔记.简要记录自己打五角星的部分,方便复习巩固.目前看到p120 Go 语言遵从的设计哲学为组合 垂直组合:类型嵌入,快速让一个类型复用其他类型已经实现的能力,实现 ...

  7. C++ 自学笔记 new和delete(动态内存分配)

    动态内存分配 Dynamic memoey allocation C++使用new和delete 来申请和释放内存 new:先申请一个空间 int\Stash : 默认构造函数初始化对象 ~:析构函数 ...

  8. C#-2 C#程序

    一 C#程序是一组类型声明 C#程序或DLL的源代码是一组一种或多种类型声明. 对于可执行程序,类型声明中必须有一个包含Main方法的类. 命名空间是一种把相关的类型声明分组并命名的方法.是类在程序集 ...

  9. jq修改多个css样式

    $("#xxx").css({"属性名称": "属性值", "属性名称": "属性值" });

  10. (数据科学学习手札144)使用管道操作符高效书写Python代码

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,一些比较熟悉pandas的读者 ...