C温故补缺(十四):内存管理
内存管理
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温故补缺(十四):内存管理的更多相关文章
- 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)
		
使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...
 - 十.oc内存管理
		
引用百度百科图 栈(stack)又名堆栈. 栈定义:栈是限定仅在表头进行插入和删除操作的线性表(有序).(又称:后进先出表) (动态)数据展示存储的地方.(举例:升降电梯)特点:先进后出(FILO—F ...
 - 《objective-c基础教程》学习笔记(十)—— 内存管理
		
本篇博文,将给大家介绍下再Objective-C中如何使用内存管理.一个程序运行的时候,如果不及时的释放没有用的空间内存.那么,程序会越来越臃肿,内存占用量会不断升高.我们在使用的时候,就会感觉很卡, ...
 - MySQL(十四)管理维护及性能优化
		
关于MySQL的学习,<MySQL必知必会>这本书呢,看完已经两个月了,一直被工作以及生活的一些琐事拖着,趁着今晚有空闲,就整理完了最后的几章学习笔记,接下来的学习计划呢, 应该是pyth ...
 - Linux命令(二十四) 磁盘管理命令(二) mkfs,mount
		
一.格式化文件系统 mkfs 当完成硬盘分区以后要进行硬盘的格式化,mkfs系列对应的命令用于将硬盘格式化为指定格式的文件系统.mkfs 本身并不执行建立文件系统的工作,而是去调用相关的程序来执行.例 ...
 - Oracle学习(十四):管理用户安全性
		
--用户(user) SQL> --创建一个名为 grace password是password 的用户,新用户没有不论什么权限 SQL> create user grace identi ...
 - OC-手动内存管理
		
一.为什么要进行内存管理 •移动设备的内存极其有限,每个app所能占用的内存是有限制的 • •下列行为都会增加一个app的内存占用 Ø创建一个OC对象 Ø定义一个变量 Ø调用一个函数或者方法 • •当 ...
 - 启动期间的内存管理之pagging_init初始化分页机制--Linux内存管理(十四)
		
1 今日内容(分页机制初始化) 在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制. 在分页机制完成后, 才会开始初始化系统的内存数据结构(包括内存节点数据和 ...
 - Spark(四十六):Spark 内存管理之—OFF_HEAP
		
存储级别简介 Spark中RDD提供了多种存储级别,除去使用内存,磁盘等,还有一种是OFF_HEAP,称之为 使用JVM堆外内存 https://github.com/apache/spark/blo ...
 - 【读书笔记】C#高级编程 第十四章 内存管理和指针
		
(一)后台内存管理 1.值数据类型 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址,该任务由Windows在后台管理(32位每个进程可使用4GB虚拟内存,64 ...
 
随机推荐
- C#/VB.NET 如何在Excel中使用条件格式设置交替行颜色
			
说起高亮数据行,不让人想起了交替颜色行,有的人把交替颜色行也都设置成高亮,不仅不美观,而且对阅读还是个干扰.隔行交替的颜色是为了阅读不串行,这些行只是环境,数据才是主体.那么如何通过C#/VB.NET ...
 - Containerd 知识点
			
1.查看安装Containerd的版本 # ctr --version ctr github.com/containerd/containerd v1.6.6 # ctr version Client ...
 - 第一个Django应用 - 第七部分:自定义admin
			
Django的admin站点是自动生成的.高度可定制的,它是Django相较其它Web框架独有的内容,广受欢迎.如果你觉得它不够美观,还有第三方美化版xadmin.请一定不要忽略它,相信我,它值得拥有 ...
 - spring boot http status 400
			
SpringBootAdmin不是Spring官方提供的模块,它包含了Client和Server两部分.server部分提供了用户管理界面,client即为被监控的服务.client需要注册到serv ...
 - Redis一键安装脚本
			
#! /usr/bin/env bash # redis 6.0.3 源码安装 # 用法: bash -x install-redis-single.sh 6.0.3 version=$1 usage ...
 - Logstash & 索引生命周期管理(ILM)
			
Grok语法 Grok是通过模式匹配的方式来识别日志中的数据,可以把Grok插件简单理解为升级版本的正则表达式.它拥有更多的模式,默认,Logstash拥有120个模式.如果这些模式不满足我们解析日志 ...
 - css百叶窗
			
效果图: css代码块: <style> *{//默认样式清除 margin: 0; padding: 0; } .content{//设置外层div的宽高,超出后隐藏 margin: 1 ...
 - VScode开发STM32/GD32单片机-环境搭建
			
1.软件下载 1.1.安装VSCode 1.2.下载安装VisualGDB 1.3.下载安装mingwin64 1.4.下载安装OpenOCD 1.5.下载GNU Arm Embedded Toolc ...
 - 2022最新版JDK1.8的安装教程、包含jdk1.8的提取码(亲测可用)
			
文章目录 1.jdk的安装 1.1.下载(百度网盘jdk1.8提取码永久有效) 1.2.双击提取出来的exe,运行程序.如下图 1.3.进入安装向导 1.4.选择默认(安装所有的组件).同时更改安装路 ...
 - 使用python获取window注册表值的方法
			
提供regfullpath的方法,可以自行封装个regpath的函数import loggingimport pywintypes import win32apiimport win32con def ...