内存四区模型
1. 行业分析
- 企业需要的C/C++工程师
- 信息系统的技术模型在分层
找出对我们初学者最近的那一层(哪些能力是你入行前,必须要掌握的)
- C项目开发的套路(一套接口)
//socket_client pool api 设计与实现
int sckClient_poolinit(void **handle);
int sckClient_getConnet(void *handle, void **hConnect);
int sckClient_sendData(void *hConnect, unsigned char *data, int dataLen);
int sckClient_getData(void *hConnect, unsigned char **data, int *dataLen);
int sckClient_getData_Free(void *hConnect, unsigned char *data);
int sckClient_putConnet(void *handle, void **hConnect);
int sckClient_pooldestory(void **handle);
总结:寻找到学习的标准
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//socket客户端环境初始化
int socketclient_init2(void **handle)
{
return 0;
}
//socket客户端报文发送
int socketclient_send2(void *handle, unsigned char *buf, int buflen)
{
return 0;
}
//socket客户端报文接受
int socketclient_recv2(void *handle, unsigned char **buf, int *buflen)
{
return 0;
}
//socket客户端环境释放
int socketclient_destory2(void **handle)
{
return 0;
}
void main()
{
printf("hello...\n");
system("pause");
return ;
}
- 基本能力
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
//排序
void main01()
{
int i = 0,j = 0;
int tmp = 0;
int a[] = {33,654,4,455,6,33,4};
printf("排序之前\n");
for (i=0; i<7; i++)
{
printf("%d ", a[i]);
}
//排序
//外层循环 当i=0的时候, 让j从1===N进行变化
//外层循环 当i=1的时候, 让j从2===N进行变化
// 当i=2的时候, 让j从3===N进行变化
//结论: 按照一个变量i不变,让另外一个变量j进行变化;下一轮 依次进行
for(i=0; i<7; i++)
{
for (j=i+1; j<7; j++) //内层循环: a[i] 和 a[j]比较
{
if (a[i] > a[j])
{
tmp = a[i];
a[i]= a[j];
a[j] = tmp;
}
}
}
printf("排序之后\n");
for (i=0; i<7; i++)
{
printf("%d ", a[i]);
}
printf("hello...\n");
system("pause");
}
//void printArray(int a[7], int num)
//void printArray(int a[], int num)
void printArray(int *a, int num)
{
int i = 0;
for (i=0; i<num; i++)
{
printf("%d ", a[i]);
}
}
void sortArray(int a[7], int num)
//void sortArray(int a[], int num)
//void sortArray(int *a, int num)
{
int i , j , tmp ;
int num2 = 0;
num2 = sizeof(a)/sizeof(a[0]);
printf("num:%d \n", num2);
//实参的a 和 形参的a 的数据类型本质不一样
//形参中的数组 ,编译器会把它当成指针处理 这是C语言的特色
for(i=0; i<num; i++)
{
for (j=i+1; j<num; j++) //内层循环: a[i] 和 a[j]比较
{
if (a[i] > a[j])
{
tmp = a[i];
a[i]= a[j];
a[j] = tmp;
}
}
}
}
//数组做函数参数的退回问题 退回为一个指针,
//1 正确做法:把数组的内存首地址和数组的有效长度传给被调用函数
//2 //实参的a 和 形参的a 的数据类型本质不一样
//形参中的数组 ,编译器会把它当成指针处理 这是C语言的特色
//排序 本质也剖析
//3 形参写在函数上,和写在函数内是一样的,只不过是具有对外的属性而已.
void main22()
{
int i = 0,j = 0;
int tmp = 0;
int num = 0;
int a[] = {33,654,4,455,6,33,4,3333};
num = 7;
num = sizeof(a)/sizeof(a[0]);
printf("num:%d \n", num);
printf("排序之前\n");
printArray(a, num);
//排序
//外层循环 当i=0的时候, 让j从1===N进行变化
//外层循环 当i=1的时候, 让j从2===N进行变化
// 当i=2的时候, 让j从3===N进行变化
//结论: 按照一个变量i不变,让另外一个变量j进行变化;下一轮 依次进行
sortArray(a, num);
printf("排序之后\n");
printArray(a, num);
printf("hello...\n");
system("pause");
}
- 培养两种能力
- 接口的封装和设计(功能抽象和封装)
- 接口api的使用能力
- 接口api的查找能力(快速上手)
- 接口api的实现能力
- 建立正确程序运行内存布局图(印象图)
- 内存四区模型图
- 函数调用模型图
- 接口的封装和设计(功能抽象和封装)
2. 数据类型本质分析
- 数据类型概念
- “类型”是对数据的抽象
- 类型相同的数据有相同的表示形式、存储格式以及相关的操作
- 程序中使用的所有数据都必定属于某一种数据类型
数据类型的本质思考
- 思考数据类型和内存有关系吗?
- C/C++为什么会引入数据类型?
数据类型的本质
- 数据类型可理解为创建变量的模具(模子);是固定大小内存的别名。
- 数据类型的作用:编译器预算对象(变量)分配的内存空间大小
数据类型大小
sizeof是操作符,不是函数;sizeof测量的实体大小为编译期间就已确定
int main()
{
int a = 10;
int b[10] ;
printf("int a:%d \n", sizeof(a));
printf("int a:%d \n", sizeof(int *));
printf("int b:%d \n", sizeof(b));
printf("int b:%d \n", sizeof(b[0]));
printf("int b:%d \n", sizeof(*b));
printf("hello.....\n");
getchar();
return 0;
}
- 数据类型别名
数据类型可以理解为固定大小内存块的别名,请问数据类型可以起别名吗?
int main()
{
//Teacher t1;
printf("Teacher:%d \n", sizeof(Teacher));
printf("u32:%d \n", sizeof(u32));
printf("u8:%d \n", sizeof(u8));
printf("hello.....\n");
getchar();
return 0;
}
数据类型的封装
void的字面意思是“无类型”,void*则为“无类型指针”,void *可以指向任何类型的数据。
- 用法1:数据类型的封装
int InitHardEnv(void **handle);
典型的如内存操作函数memcpy和memset的函数原型分别为
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
用法2: void修饰函数返回值和参数,仅表示无。
如果函数没有返回值,那么应该将其声明为void型
如果函数没有参数,应该声明其参数为voidint function(void) { return 1; }
void指针的意义
C语言规定只有相同类型的指针才可以相互赋值
void*指针作为左值用于“接收”任意类型的指针
void*指针作为右值赋值给其它指针时需要强制类型转换
int *p1 = NULL;
char *p2 = (char *)malloc(sizoeof(char)*20);不存在void类型的变量
C语言没有定义void究竟是多大内存的别名- 扩展阅读《void类型详解.doc》
- 用法1:数据类型的封装
typedef的用法。
数据类型总结与扩展
- 数据类型本质是固定内存大小的别名;是个模具,c语言规定:通过数据类型定义变量。
- 数据类型大小计算(sizeof)
- 可以给已存在的数据类型起别名typedef
- 数据类型封装概念(void 万能类型)
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
// 数据类型的用途
//数据类型的本质:固定大小内存块的别名
// b &b 数组数据类型 (定义一个1 数组类型 2数组指针 3 数组类型和数组指针类型的关系) ====>压死初学者的三座大山 抛砖
//
void main31()
{
int a; //告诉c编译器分配4个字节的内存
int b[10] ; //告诉c编译器分配40个字节内存
printf("b:%d, b+1:%d, &b:%d, &b+1:%d \n", b, b+1, &b, &b+1);
printf("sizeof(b):%d \n", sizeof(b)); //40
printf("sizeof(a):%d \n ", sizeof(a)); //4
// b+1 &b+1 结果不一样 //b &b所代表的数据类型不一样
//b 代表的数组首元素的地址
//&b代表的是整个数组的地址
//
printf("hello....\n");
system("pause");
}
struct Teacher
{
char name[64];
int age;
}Teacher;
typedef struct Teacher2
{
char name[64];
int age;
}Teacher2;
//数据别名 typedef
typedef int u32;
void main33()
{
int a; //告诉c编译器分配4个字节的内存
int b[10] ; //告诉c编译器分配40个自己内存
struct Teacher t1;
Teacher2 t2;
t1.age = 31;
printf("u32:%d \n", sizeof(u32));
{
char *p2 = NULL;
void *p1 = NULL;
p2 = (char *)malloc(100);
p1 = &p2;
}
{
//void a;//编译器不知道如何分配内存
}
printf("hello....\n");
system("pause");
}
思考1:C一维数组、二维数组有数据类型吗?int array[10]。
- 若有,数组类型又如何表达?又如定义?
- 若没有,也请说明原因。
- 抛砖:数组类型,压死初学者的三座大山
- 数组类型
- 数组指针
- 数组类型和数组指针的关系
思考2: C语言中,函数是可以看做一种数据类型吗?
- 若是,请说明原因,并进一步思考:函数这种数据类型,能再重定义吗?
- 若不是,也请说明原因。
3. 变量本质分析
- 变量概念
概念:既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称为常量。
变量定义形式: 类型 标识符, 标识符, … , 标识符 ;
例如:
int x ;
int wordCut , Radius , Height ;
double FlightTime , Mileage , Speed ;
- 变量本质
- 程序通过变量来申请和命名内存空间 int a = 0
- 通过变量名访问内存空间
(一段连续)内存空间的别名(是一个门牌号) - 修改变量有几种方法?
- 直接
- 间接。内存有地址编号,拿到地址编号也可以修改内存;于是指针横空出世了!(编程案例)
- 内存空间可以再取别名吗?
- 数据类型和变量的关系:通过数据类型定义变量
- 总结及思考题
- 对内存,可读可写
- 通过变量往内存读写数据
- 不是向变量读写数据,而是向变量所代表的内存空间中写数据
- 问:变量跑哪去了?
- 变量三要素(名称、大小、作用域),变量的生命周期?
- C++编译器是如何管理函数1,函数2变量之间的关系的?
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void main44()
{
int a ;
int b;
char * p ;
//p = 0xaa11
a = 10; //1 直接赋值 //cpu里面执行
printf("&a: %d\n", &a);
//2间接赋值 ==直接通过内存
*((int*)1245024) = 200;
printf("a: %d\n", a);
{
p = 1245024; // 间接赋值 通过指针
*p = 300;
}
//3 对内存空间能不能在取 别名..... C++ 引用 抛砖.....
//1245024
printf("hello...\n");
system("pause");
return ;
}
void sub1()
{
}
4. 程序的内存四区模型
- 内存四区的建立流程
流程说明
1. 操作系统把物理硬盘代码load到内存
2. 操作系统把c代码分成四个区
3. 操作系统找到main函数入口执行
各区元素分析
全局区的内存模型
1. 常量存储在全局区
2. 编译器会优化代码,对相同的常量只在全局区分配一段内存,使用相同的地址
3. 指针变量和指针指向的内存空间的变量是两码事,不要混淆
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *getStr1(void)
{
char * p1 = "AlohaJack";//常量不会在函数返回后销毁,只在程序结束后销毁
return p1;
}
char *getStr2(void)
{
char * p2 = "AlohaJackBoom";//常量不会在函数返回后销毁,只在程序结束后销毁
return p2;
}
char *getStr3(void)
{
char * p3 = "AlohaJack";//常量不会在函数返回后销毁,只在程序结束后销毁
return p3;
}
int main(void)
{
char *p1 = NULL;
char *p2 = NULL;
char *p3 = NULL;
p1 = getStr1();//p1和p3的常量一模一样,共用一段内存空间
p2 = getStr2();
p3 = getStr3();
printf("p1:%s\np2:%s\np3:%s\n",p1,p2,p3);
printf("p1:%d\np2:%d\np3:%d\n", p1, p2, p3);
system("PAUSE");
return 1;
}
注意:子函数中的p1和p2会在函数调用完成以后自动销毁
- 堆区栈区的模型
- 堆区的内存有程序员分配释放,或者程序员没有释放的情况下,有操作系统在程序结束后释放
- 堆区的内存可以在子函数分配(申请),在主调函数里继续使用,因为他并没有随着函数的返回而销毁
示例代码
#define _CRT_SECURE_NO_WARNINGS//防止编译器对strcpy报错
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * getMemFromHeap(int size)
{
char *p = NULL;
p = (char*)malloc(size);//在子函数中分配堆内存并返回首地址
if (NULL == p)
exit(1);
else
return p;
}
int main(void)
{
char * tmp = NULL;
tmp = getMemFromHeap(10);//主函数中获取在子函数中分配到的内存并使用
strcpy(tmp, "Aloha");
printf("tmp:%s\n",tmp);
system("PAUSE");
return 1;
}
* 栈区的内存在函数返回后随即被释放
*虽然指向之前位置的指针指向的内存空间的地址没有变,但实际上内存空间的内容是未知的,与编译器的具体行为有关,所以有时候release和debug版本堆栈区内存空间的引用有不同的结果
* 在主调函数引用子函数栈区的内存空间有可能引起系统崩溃或者其它异常
示例代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * getMemFromHeap(int size)
{
char *p = NULL;
p = (char*)malloc(size);
if (NULL == p)
exit(1);
else
return p;
}
char * getMemFromStack(void)
{
char buf[64];
return buf;
}
int main(void)
{
char * tmp = NULL;
tmp = getMemFromStack();
strcpy(tmp, "Aloha");
printf("tmp:%s\n",tmp);
system("PAUSE");
return 1;
}
可见引用栈区的内存空间是极其不安全的
- 栈的属性简介
- 先进后出
- 堆和栈的生长方向可以通过代码打印变量的地址值测试出来
5. 函数调用模型
- 基本原理
内存四区模型和函数调用模型变量传递分析
一个主程序有n函数组成,c++编译器会建立有几个堆区?有几个栈区?
答:操作系统职位应用程序分配一个内存四区函数嵌套调用时,实参地址传给形参后,C++编译器如何管理变量的生命周期?
分析:函数A,调用函数B,通过参数传递的变量(内存空间能用吗?)