重学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语言中的数据类型

      数据类型本质是不同大小内存块的别名,且不同的数据类型对应着不同的解析方式,这是我对数据类型的认识,下面举例介绍。

1 #include<stdio.h>
2 #include<stdlib.h>
3 
4 void main()
5 {
6    printf("%d\n",sizeof(int));
7    printf("%d\n",sizeof(char));
8    system("pause") ;
9 }

      上面是一个较完整的命令行窗口的代码块,稍微学过C的应该可以看懂上面的代码,printf函数可以在命令行中输出字符串,sizeof函数可以计算类型或者变量在内存中占用的大小(以byte为单位)。 变量是在通过数据类型定义后即占用了该类型对应的内存大小。32位程序下上面代码的运行结果应该如下(其他位数系统可能不同,这里仅以32位系统举例):

 

4
1
请按任意键继续. . .

      这说明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,就会溢出,会使变量变成其类型的最小值。

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 
 4 void main()
 5 {
 6     unsigned int ui = 4294967295;
 7     int i = 2147483647;
 8     printf("i=%d\n", i);
 9     printf("ui=%u\n", ui);
10     system("pause");
11 }

      根据以上可以推断,char占用的1byte内存空间可以表示的值范围为-128至+127;同样,unsigned char 可表示的值范围为0-255。那么int和char除了占用的内存空间不同还有什么区别?先看下面的测试代码:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 
 4 void main()
 5 {
 6     int i = 90;
 7     int j = c;
 8     char a = 100;
 9     char b = b;
10     printf("作为整数输出:");
11     printf("i=%d,j=%d,a=%d,b=%d\n", i, j, a, b);
12     printf("作为字符输出:");
13     printf("i=%c,j=%c,a=%c,b=%c\n", i, j, a, b);
14     system("pause");
15 }

      首先,上面这段代码应该是编译通过的,看过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类型可能就得不出这样反应其映射性关系的结果,因为它们的解析完全不同。

 

 

 

 

 

内存四区

     本文主要将内存,之前都是一些零碎的知识,现在具体来说说C语言中的内存;C语言中用户可以操作的内存基本都是定义变量或者通过malloc或者relloc这类函数分配到的,如果要获取变量的地址,可以通过&符号。

     C语言程序的内存,传统意义来说分为四个区,分别是 代码区、数据区(全局区、常量区)、栈区(临时区)、堆区。

     代码区存放编译器编译C语言程序后的二进制代码,C语言中无法获取其地址,所以不过多说明。

     数据区,是存储程序全局变量的地方,其中全局变量放在数据区的全局区域,常量放在全局区的常量区域,常量不能获取地址,全局变量可以。该区域内存会在程序结束后释放。

     栈区(临时区),程序直接定义的变量都放在栈区,在变量所在函数执行完成的时候,函数内部定义的栈区变量会被释放(释放的意义是不能再正确取得该变量的值)。每个程序所能用的栈区内存很有限,一般只有1M。因为其在函数调用完成后即被释放的特性,所以也被称为临时区。

     堆区,通过malloc或者relloc这类函数分配的内都存放在堆区,一般使用堆区的原因是需要用到大块的内存,或者是希望变量在其所在函数调用结束后变量内存仍能使用,那么就会使用到堆区,堆区的缺点是内存需要手动使用free函数释放,容易造成内存泄漏(指的是变量使用完了仍然占着内存空间,如果占用过大会影响系统或其他程序运行)。C语言中用的最多的内存区即堆区和栈区。下面代码是内存四区变量的示例:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 
 4 int x = 0; // 数据区 全局区变量
 5 int y;// 数据区 全局区变量  x,y都会在程序结束时释放内存
 6 
 7 
 8 int * ExampleStack()
 9 {
10     int i = 0;
11     int j; // i和j是栈区变量, 虽然i比j先定义但i的内存地址比j大,在Example函数结束后,i和j就会被释放,无法再访问
12     return &i;  // 返回栈区地址,读取值的时候可以发现值不是0,说明内存被重新分配了。
13 }
14 
15 int *ExampleHeap()
16 {
17     // 变量p本身在栈区,存储着malloc分配的内存首地址,使用*可以取地址的值,而*p在堆区
18     int *p = (int *)malloc(sizeof(int)); 
19     *p = 20;
20     return p;  // 返回变量p,读取值正常
21 }
22 
23 void main()
24 { 
25     int *p = ExampleStack();
26     int *q = ExampleHeap();
27     char *str = "abcd"; // str本身在栈区,而"abcd"在数据区 常量区,且str只读
28     printf("栈区:%d\n", *p); // 这里打印出的p是一个垃圾值,说明ExampleStack结束的时候已经把i的内存释放
29     printf("堆区:%d\n", *q); // 这里打印出20,说明变量q中的内存地址没有被释放
30     if (q != NULL) free(q); // 释放内存
31     q = NULL;// 防野指针
32     p = NULL;// 防野指针 
33     system("pause");
34 }

     代码中除了主main函数还有ExampleStack和ExampleHeap函数作为示例,为方便作为例子还使用了指针,指针本身就不做过多介绍,不懂的话可以在了解指针知识后再来看这段代码。

     本文只是总结了自己对C语言的一些认识,并不能直接当成教程使用,但可以作为初学者学习借鉴的知识来看。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。