C提高(1)/内存四区

3/8/2017来源:ASP.NET技巧人气:1638

我们经常听别人说:C语言是一门偏向于底层(硬件)的语言。偏向于硬件的特点之一就是能够对内存进行直接的操作,今天我们就来讲一讲C语言中的内存是如何分配的。

通常情况下C语言中,内存分为栈区,堆区,全局区(静态区,常量区),代码区。

内存区域 Cool
栈区 1.编译器分配和释放 2.容量有限,但速度快
堆区 1.用户手动分配和释放 2.容量大,但分配方式繁琐
全局区 静态区 全局变量的存储和静态变量的存储在同一区域,这一区域在程序运行完后,由操作系统释放
常量区 字符串常量和其他常量,在程序运行完后,由操作系统释放
代码区 存放函数体二进制代码

也就是说,虽然在硬件上,内存只是一个长条状的硬件设备。 内存 但是,计算机的内存在逻辑上可以分为以上4个部分。而C语言的特点就是操作不同内存区域,来实现相应的功能。

想要知道C语言是如何操作内存的,我们先从数据类型和变量开始讲起。

数据类型

数据类型的概念 类型是对数据的抽象。 具有相同类型的数据具有相同的表示形式,存储格式以及运算。 C程序中,所有数据都要有相应的数据类型。

数据类型 分类 具体类型
simple(基本) 整型 int
浮点型 float double
字符型 char
空型 void
simple(用户自定义) 枚举类型 enum
结构化 数组 []
结构体 struct
联合体 union
指针类型 *

数据类型的本质 数据类型是创建变量的模具,是固定内存块大小的别名。他的作用就是告诉编译器分配多大的内存空间。

数据类型的大小 如何查看某一数据类型的大小?使用运算符sizeof() sizeof能够在编译时将具体数据类型的大小计算出来。 注意:不同的数据类型的大小不是由C语言规定,而是由操作系统决定。

int a = 10; //int 就是告诉编译器 开辟固定大小4个字节的内存块 double b = 1.30;//就是告诉编译器 开辟固定大小8个字节的内存块 char c = 'c'; //数据类型的他的本质是 固定大小内存看的别名 a = 30; int * p = &a; //p是一个指针, 指向a的地址, char *pp = &c; int b_array[10] = { 0 }; //定义一个数组 PRintf("sizeof(p) :%d\n", sizeof(p)); //求 int* 数据类型的大小 4 printf("sizeof(pp) :%d\n", sizeof(pp));//求char*数据类型的大小 4 printf("sizeof(*p):%d\n", sizeof(*p)); // 求int数据类型的大小 4 printf("sizeof(*pp):%d\n", sizeof(*pp));//求 char数据类型的大小 1 printf("p : %p, p+1: %p\n", p, p + 1); //指针的数据类型 根据指针的跨度来决定的 printf("pp : %p, pp+1: %p\n", pp, pp + 1); //int---4 //double --8 //int * ---4; //int[10] ----40 //求数组类型的大小 sizeof printf("sizeof(b_array) : %d\n", sizeof(b_array)); //求 int b_array[10]数据的大小 printf("sizeof(b_array[0]): %d\n", sizeof(b_array[0])); //求int这种数据类型的大小 printf("&b_array:%p, b_array:%p, &b_array+1: %p, b_array+1: %p", &b_array, b_array, &b_array + 1, b_array + 1); //b_array 他是数组首元素的地址,首元素是int类型 b_array的数据类型 int* //&b_array 他是数组(int[10])的地址, 此数组的类型 int[10] ,, &b_array-> (int[10]) * student_t s1; //分配 68 的字节内存块 student_t *sp = &s1; printf("sizeof(student_t) : %d\n", sizeof(s1));

数据类型的别名 如何给数据类型起别名?使用关键字typedef

typedef struct student //给一个结构体数据类型 起别名 { char name[64]; int age; } student_t; //_t表示这个数据类型是由typedef定义的 typedef unsigned int u32; typedef unsigned long u64;

数据类型的封装 void * void*与void 的差别跟java和JavaScript的差别一样大。

void只是一个语法,用来表明函数参数为空或者函数的返回值为空 而void * 则表示万能的指针,他做左值能够接收所有的指针。当他做右值时,需要强制类型转换。

为什么万能指针这么重要? 因为C中规定,不同基类型的指针不能互相赋值。这个时候就需要用万能指针来完成数据类型的封装了。

总结 提出两个思考题

C语言中一维数组和二维数组有数据类型吗? C语言中函数可以看做是一种数据类型吗? 数组类型和数组指针之间的联系

变量

变量的概念 我们上面介绍了数据类型,数据类型的作用就是用来定义变量,告诉编译器要开辟多少内存空间给某一变量。但实际上,这并不是变量最与众不同的地方。因为我们通常提的变量都是和常量相对的一个概念。 即能读又能写的内存对象,称为变量 只能读不能写的内存对象,称为常量(一旦初始化后就不能修改的量) 变量的本质 程序通过变量的定义来申请和命名内存空间 通过变量名来访问内存空间 变量是所分配内存块的别名 变量的修改方法 直接法–变量名 间接法–指针 数据类型和变量的关系 数据类型定义变量。编译器通过栈的方式给变量分配内存。

讲完了数据类型和变量,我们就可以进入本节的主题了。

什么是栈 栈是分配内存的一种方式。栈所分配的内存在内存中全部都集中在某一区域,称为栈区。

栈的功能

函数调用 函数的调用是通过压栈和出栈实现的 在下面,我会举例详细说明。 表达式求值 我们程序中的表达式的值的计算是通过栈来完成的 内存分配 局部变量的内存分配都是由栈完成的。比如:int a; 变量a的内存分配是由栈来完成的。 内存缓冲 因为栈有LIFO(Last In First Out)的特性,可以用来做内存缓冲 栈有一个有趣的应用–走迷宫 栈的特点 栈区是由操作系统自动管理的,内存的分配和释放都不需要程序员参与。但栈的空间有限(3m),所以如果申请占用超大内存的局部变量,很容易爆栈。或者多级函数调用,也容易爆栈。

什么是堆 堆是分配内存的一种方式。堆所分配的内存在内存中全部都集中在某一区域,称为堆区。

堆的功能 动态分配内存,常用于跨函数使用内存。

堆的特点 空间大(3G),当所处理的数据大到栈无法处理的时候,我们就需要使用堆来对数据进行操作。这就考验我们的编程功底了。 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> //栈 void test() { int a = 10; //局部变量 在栈上开辟内存 是由操作系统自动在栈上开辟的 int b = 20; int array[10]; { int c; int d; } } //全局区包括 静态区(全局变量和 静态的局部或者全部变量) 和常量区 int g_a; //堆 void test2() { char * p = NULL; // 在栈上开辟了一个char *型的指针变量p占4个字节。 char *str = "1234567";在全局区的常量区开辟了8个字节存放字符串"1234567",同时在栈上开辟了一个char *型的指针变量str占4个字节 //在堆上开辟空间 p = (char*)malloc(100); //malloc分配的内存空间没有数据类型为void * ,这个时候在前面要加上强制类型转换 if (p == NULL) { fprintf(stderr, "malloc error\n"); return; } static int b = 20; //释放堆的空间 if (p != NULL) { free(p); p = NULL; } } int main(void) { return 0; }

补充:

函数的调用–内存模型 我们在上面提到过函数的调用是一个进栈出栈的过程。我们以下面的一个简单程序为例,分析一下该过程。

函数压栈1

压栈

栈的开口方向 我们规定,内存从下往上地址从小到大。即0x00000000到0xffffffff。那么在一般的操作系统中,栈的开口方向都是向下的。 栈 测试程序:

#include<stdio.h> int main(void) { int a; int b; char buf[4]; //进栈顺序为 a b buf[4] printf("&a:%p\n",&a); printf("&b:%p\n",&b); //可以看到a的地址高于b的地址 printf("buf的地址:%p\n",&buf[0]); printf("buf+1地址:%p\n",&buf[1]); //可以看到buf1的地址高于buf0的地址 return 0; }

作业: 画出一下代码的内存四区图:

#include <stdio.h> #include <string.h> #include <stdlib.h> char *get_mem(int size){ char *p2 = NULL; //分配4个字节的内存 栈区也叫临时区 p2 = (char *)malloc(size); return p2; } int main(void){ char buf[100]; int a = 10; //分配4个字节的内存 栈区也叫临时区 int *p; //分配4个字节的内存 p = &a; //cpu执⾏行的代码,放在代码区 *p = 20; char *mp = get_mem(100); strcpy(mp, "ABCDEFG"); if (mp != NULL) { free(mp); mp = NULL; } return 0; }

内存四区例子

最后,给大家一个建议。C语言的学习跟内存的操作息息相关,因此,学习理解C语言的特性最好的方法就是画图。通过内存四区图的分析,我们能够对C语言有一个更深入的了解,这对我们的学习是大有裨益的。