C/C++心得-从内存开始
因工作与自身各方面需要,开始重新学C,其实说重新也不太准,原来只是大学里面接触过,且还未得多少精髓就转其他开发,不过也正是因此才有了重新学习的必要,基础部分的心得将通过博文记录下来,对于初学者应该有些用处;当然这里只是心得,并不能直接用来学习C,但应该可以少走一些弯路。
看了不少资料,最终我个人的认识是:C/C++的难点在C,C的精华是指针,想学好指针就要清楚的认识计算机内存。
1.了解内存必要知识
介绍内存就先从存储单位开始;写这篇文章的时间是在2014年冬季,当时日常生活中我们接触到的存储设备是电脑硬盘、手机内存卡;最常接触的存储单位是MB(读作“兆”)和GB(读作“吉”)。而实际上,计算机语言中最小的存储单位是bit(读作“比特”),计算机中许多数据的表示都是以二进制做单位的,而1比特就代表一个二进制数位,只能存储0或者1;Byte是计算机中最常用的单位,8bit为1byte,许多地方以1b代表1bit,1B代表1byte。1 Kilobyte等于1000byte(计算机中为二进制计算1kilobyte等于2的十次方byte,也就是1024byte)。下面介绍一些其他单位的换算:
1 kilobyte kB = 1000 (10^3) byte
1 megabyte MB = 1 000 000 (10^6) byte
1 gigabyte GB = 1 000 000 000 (10^9) byte
1 terabyte TB = 1 000 000 000 000 (10^12) byte
1 petabyte PB = 1 000 000 000 000 000 (10^15) byte
1 exabyte EB = 1 000 000 000 000 000 000 (10^18) byte
1 zettabyte ZB = 1 000 000 000 000 000 000 000 (10^21) byte
1 yottabyte YB = 1 000 000 000 000 000 000 000 000 (10^24) byte
1 brontobyte BB = 1 000 000 000 000 000 000 000 000 000 (10^27)byte
1 nonabyte NB = 1 000 000 000 000 000 000 000 000 000 000 (10^30) byte
上面也是存储介质(如:硬盘)厂商使用的单位,而在计算机系统中识别是使用二进制的千来换算,所以识别的时候容量总会小一些,二进制换算如下:
1 kilobyte kB = 1024 (2^10) byte
1 megabyte MB = 1 048 576 (2^20) byte
1 gigabyte GB = 1 073 741 824 (2^30) byte
1 terabyte TB = 1 099 511 627 776 (2^40) byte
1 petabyte PB = 1 125 899 906 842 624 (2^50) byte
1 exabyte EB = 1 152 921 504 606 846 976 (2^60) byte
1 zettabyte ZB = 1 180 591 620 717 411 303 424(2^70) byte
1 yottabyte YB = 1 208 925 819 614 629 174 706 176 (2^80) byte
1 brontobyte BB = 1 237 940 039 285 380 274 899 124 224(2^90)byte
1 nonabyte NB = 1 267 650 600 228 229 401 496 703 205 376(2^100) byte
当然,其实TB后面的单位基本都用不到,这些东西做个了解,记住计算机系统中除了开始的bit和byte,后面的单位都是前面的1024倍即可;
32位系统中的32位某些意义上指的是CPU一次能处理的最大位数,每一位的单位为byte,以二进制计算,所以32位系统CPU一次能处理的值为2的32次方,最后得出4GB,所以32位系统支持的理论最大内存为4GB,当然实际情况下可能会小一些。
2.C语言中的数据类型
数据类型本质是不同大小内存块的别名,且不同的数据类型对应着不同的解析方式,这是我对数据类型的认识,下面举例介绍。
#include<stdio.h>
#include<stdlib.h> void main()
{
printf("%d\n",sizeof(int));
printf("%d\n",sizeof(char));
system("pause") ;
}
上面是一个较完整的命令行窗口的代码块,稍微学过C的应该可以看懂上面的代码,printf函数可以在命令行中输出字符串,sizeof函数可以计算类型或者变量在内存中占用的大小(以byte为单位)。 变量是在通过数据类型定义后即占用了该类型对应的内存大小。32位程序下上面代码的运行结果应该如下(其他位数系统可能不同,这里仅以32位系统举例):
请按任意键继续. . .
这说明int类型在内存中占有4byte,char占有1byte。先来说说数字本身,即4和1究竟有什么意义?其实这个数字表明该类型所能表示多少种不同的值,比如int占4字节,也就是32bit,而前面说了,1bit可以表示0和1两种值,那么int可以表示2的32次方种不同的值,即4294967296种不同的值。当然这并不是说int类型最大可以设置为4294967296,因为int类型本身还有负数,实际来说int类型值的范围负数非负数各占一半,即表示范围为:-2147483648至+2147483647;有时候并用不到负数,所有可以在int前面加上unsigned,即unsigned int,这种类型值的范围为:0至+4294967295。可以利用下面代码做测试,在这类数上再加1,就会溢出,会使变量变成其类型的最小值。
#include<stdio.h>
#include<stdlib.h> void main()
{
unsigned int ui = ;
int i = ;
printf("i=%d\n", i);
printf("ui=%u\n", ui);
system("pause");
}
根据以上可以推断,char占用的1byte内存空间可以表示的值范围为-128至+127;同样,unsigned char 可表示的值范围为0-255。那么int和char除了占用的内存空间不同还有什么区别?先看下面的测试代码:
#include<stdio.h>
#include<stdlib.h> void main()
{
int i = ;
int j = 'c';
char a = ;
char b = 'b';
printf("作为整数输出:");
printf("i=%d,j=%d,a=%d,b=%d\n", i, j, a, b);
printf("作为字符输出:");
printf("i=%c,j=%c,a=%c,b=%c\n", i, j, a, b);
system("pause");
}
首先,上面这段代码应该是编译通过的,看过C数据类型的都知道,int是存储整数的数据类型,char是存储字符的数据类型,但是上面代码中可以为int类型变量j赋值字符,也可以为char类型变量a赋值整数,且上面4个变量都可以用%d作为整数输出,也都可以用%c作为字符输出。这就是所谓的解析方式了,数据类型除了其分配的内存大小不同,解析方式也不同,而int和char类型的解析方式可以相互间通用。我们都知道计算机最后的数据都会转换为0和1的二进制数据,0到9之间的数字也都是通过二进制转换而来的,那么字符其实也是通过数字转化而来的,二进制到十进制我们可以直接计算出来,但数字到字符的转化,是人为规定的,这个规定的表就是ASCII表。ASCII表的具体对应关系这里就不贴了,各类C资料上应该都有。上面代码的执行结果如下:
作为整数输出:i=90,j=99,a=100,b=98
作为字符输出:i=Z,j=c,a=d,b=b
请按任意键继续. . .
int和char类型可以相互转化是因为ASCII表对应的映射关系,且我们上面代码中的测试数据没有越界(没有超过char类型值的范围),从上面的执行结果来看,我们知道ASCII中整数90对应大写字母Z,整数99对应小写字母c,整数100对应小写字母d,整数98对应小写字母b。因为用于测试,所以我用了char和int类型,如果使用float或者double类型可能就得不出这样反应其映射性关系的结果,因为它们的解析完全不同。
3.内存四区
本文主要将内存,之前都是一些零碎的知识,现在具体来说说C语言中的内存;C语言中用户可以操作的内存基本都是定义变量或者通过malloc或者relloc这类函数分配到的,如果要获取变量的地址,可以通过&符号。
C语言程序的内存,传统意义来说分为四个区,分别是 代码区、数据区(全局区、常量区)、栈区(临时区)、堆区。
代码区存放编译器编译C语言程序后的二进制代码,C语言中无法获取其地址,所以不过多说明。
数据区,是存储程序全局变量的地方,其中全局变量放在数据区的全局区域,常量放在全局区的常量区域,常量不能获取地址,全局变量可以。该区域内存会在程序结束后释放。
栈区(临时区),程序直接定义的变量都放在栈区,在变量所在函数执行完成的时候,函数内部定义的栈区变量会被释放(释放的意义是不能再正确取得该变量的值)。每个程序所能用的栈区内存很有限,一般只有1M。因为其在函数调用完成后即被释放的特性,所以也被称为临时区。
堆区,通过malloc或者relloc这类函数分配的内都存放在堆区,一般使用堆区的原因是需要用到大块的内存,或者是希望变量在其所在函数调用结束后变量内存仍能使用,那么就会使用到堆区,堆区的缺点是内存需要手动使用free函数释放,容易造成内存泄漏(指的是变量使用完了仍然占着内存空间,如果占用过大会影响系统或其他程序运行)。C语言中用的最多的内存区即堆区和栈区。下面代码是内存四区变量的示例:
#include<stdio.h>
#include<stdlib.h> int x = ; // 数据区 全局区变量
int y;// 数据区 全局区变量 x,y都会在程序结束时释放内存 int * ExampleStack()
{
int i = ;
int j; // i和j是栈区变量, 虽然i比j先定义但因栈的特点i的内存地址比j大,在Example函数结束后,i和j就会被释放,无法再访问
printf("i地址:%x,j地址:%x\n", &i, &j);// 比较两个内存地址大小
return &i; // 返回栈区地址,读取值的时候可以发现值不是0,说明内存被重新分配了。
} int *ExampleHeap()
{
// 变量p本身在栈区,存储着malloc分配的内存首地址,使用*可以取地址的值,而*p在堆区
int *p = (int *)malloc(sizeof(int));
// 堆特点,p比q先定义,p内存地址比q小
int *q = (int *)malloc(sizeof(int));
printf("p地址:%x,q地址:%x\n", p, q);// 比较两个内存地址大小
*p = ;
free(q);
q = NULL;
return p; // 返回变量p,读取值正常
} void main()
{
int *p = ExampleStack();
int *q = ExampleHeap();
char *str = "abcd"; // str本身在栈区,而"abcd"在数据区 常量区,且str只读
printf("栈区:%d\n", *p); // 这里打印出的p是一个垃圾值,说明ExampleStack结束的时候已经把i的内存释放
printf("堆区:%d\n", *q); // 这里打印出20,说明变量q中的内存地址没有被释放
if (q != NULL) free(q); // 释放内存
q = NULL;// 防野指针
p = NULL;// 防野指针
system("pause");
}
代码中除了主main函数还有ExampleStack和ExampleHeap函数作为示例,为方便作为例子还使用了指针,指针本身就不做过多介绍,不懂的话可以在了解指针知识后再来看这段代码。
本文只是总结了自己对C语言的一些认识,并不能直接当成教程使用,但可以作为初学者学习借鉴的知识来看。
C/C++心得-从内存开始的更多相关文章
- JVM学习心得—JVM内存模型(个人整理,请勿转载)
一.运行时数据区域 线程私有的:程序计数器+虚拟机栈+本地方法栈 线程共享的:堆+方法区(运行时常量池)+直接内存(非运行时数据区的一部分) *JDK1.8后将方法区废除,新增元空间. 1.1 程序计 ...
- netty学习心得2内存池
http://frankfan915.iteye.com/blog/2199600 https://www.jianshu.com/p/13f72e0395c8:一个性能调优的文档,还有一些linux ...
- 感悟优化——Netty对JDK缓冲区的内存池零拷贝改造
NIO中缓冲区是数据传输的基础,JDK通过ByteBuffer实现,Netty框架中并未采用JDK原生的ByteBuffer,而是构造了ByteBuf. ByteBuf对ByteBuffer做了大量的 ...
- Union函数
. 共用体声明和共用体变量定义 共用体(参考“共用体”百科词条)是一种特殊形式的变量,使用关键字union来定义 共用体(有些人也叫"联合")声明和共用体变量定义与结构体十分相似. ...
- sqlserver内存释放心得
SQL Server 2008 或者R2的默认内存分配是2147483647MB, 差不多算是无穷大,对于系统内存的管理策略是有多少占多少.SQLserver会把所有处理过的SQL操作缓存在内存里,这 ...
- ios 内存管理 心得
- alloc, copy, retain会把引用计数+1 - release会把引用计数-1 - 局部变量如果初始化时不是autorelease的,要及时调用release释放,并且赋值为nil否则 ...
- 使用kyototycoon挂载leveldb,映射内存磁盘的使用心得
前段时间在做大数据的KV引擎应用,测试了leveldb的性能,感觉挺好的,美中不足的是他是基于磁盘读写.在我们的场景里,IO频率预计会远远超出磁盘的承受能力,并且太频繁的读取可能也会引发磁盘恶化的速度 ...
- Linux的php-fpm优化心得-php-fpm进程占用内存大和不释放内存问题(转)
原文地址:https://wzfou.com/php-fpm/ 最近发现博客的内存老是隔三差五地被“吃掉”了,登录到后台后偶尔会出卡顿的情况,一开始怀疑是Swap不够导致的,于是给VPS主机增加了几个 ...
- c# 内存泄漏检查心得
系统环境 windows 7 x64 检查工具:ANTS Memory Profiler 7 或者 .NET Memory Profiler 4.0 开发的软件为winform / windows s ...
随机推荐
- Grunt:任务自动管理工具(收藏+转载)
原文:http://javascript.ruanyifeng.com/tool/grunt.html 安装 命令脚本文件Gruntfile.js Gruntfile.js实例:grunt-contr ...
- Centos时间查看修改命令date详解
1.查看.修改Linux时区与时间 一.linux时区的查看与修改 1,查看当前时区date -R 2,修改设置时区方法1:tzselect 方法2:仅限于RedHat Linux 和 CentOSt ...
- SQL语句整理(二) 数据定义语言DDL
前言: 这是我学数据库时整理的学习资料,基本上包括了所以的SQL语句的知识点. 我的教材是人大王珊老师的<数据库系统概论>. 因为是手打的,所以会用一些细节打错了,但都挺明显也不多(考完试 ...
- spring 代理
java动态代理实现 1. Java自带的动态代理,反射生成字节码 2. Cglib调用asm生成子类 spring 中代理实现 1. 如果类实现了接口,使用java动态代理 2. 没有实现接口,使用 ...
- thinkphp 手机号和用户名同时登录
//在注册时用户名不能是纯数字, 防止一个人的用户名和另一个人的手机号相同public function Login(){ if (IS_AJAX) { $username = I('param.us ...
- ES6中的import()函数
import(specifier) 上面代码中,import函数的参数specifier,指定所要加载的模块的位置.import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要 ...
- 微信小程序request请求之GET跟POST的区别
1.GET 例子: wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '' , y: '' }, header: { 'content ...
- 2-4 Sass的函数功能-颜色函数
RGB颜色函数-RGB()颜色函数 在 Sass 的官方文档中,列出了 Sass 的颜色函数清单,从大的方面主要分为 RGB , HSL 和 Opacity 三大函数,当然其还包括一些其他的颜色函数, ...
- 机智的Popup,带着简单的图表感觉酷酷的
之前有提过用 InfoTemplate 类对 FeatureLayer 的基本信息进行展示,今天为大家介绍 esri/dijit/Popup 的使用,这东西还有 简单的图表展示功能呢! <!DO ...
- C++基础--回调的应用
一.类成员函数的回调 1. 类成员函数的回调,函数的调用必须通过类来调用: CallBack.h #pragma once class CallBack { public: CallBack(); ~ ...