//
// main.m
// C09 动态内存分配
//
// Created by 康亮亮 on 15/10/19.
// Copyright (c) 2015年 Darling.com. All rights reserved.
//
#import <Foundation/Foundation.h>
// 静态变量(全局变量)
// d的在整个工程中都有效
int d = 12;
// e的作用只在当前文件中
static int e = 23;
void testMemory(){
printf("hello");
}
int main(int argc, const char * argv[]) {
// 按地址从高到低
// 栈内存中返回地址是不安全的,要避免!先进后出,局部变量丢在栈区
// 堆区,手动分配,手动释放!
// 静态存储区 全局变量(不属于任何一个函数)丢在静态存储区
// 常量占用内存,只读状态,不可修改!
// 代码区
#pragma mark 内存五大区
/*
1、内存地址按照从高到低可以分为五个区域,每个区域用来存放不同类型的数据
2、五大区域按地址从高到低:栈区、堆区、静态区、常量区、代码区。
*/
// 打印各个区的地址
// 1、栈区的地址
// int a = 23;
// printf("栈区的地址是:%p\n",&a);
// // 2、堆区的地址
// int *p = malloc(4);
// printf("堆区的地址是:%p\n",p);
// // 3、静态区的地址
// static int b = 12;
// printf("静态区的地址是:%p\n",&b);
// // 4、常量区的地址
// char *p1 = "hello";
// printf("常量区的地址是:%p\n",p1);
// // 5、代码区的地址
// printf("代码区的地址是:%p\n",testMemory);
#pragma mark 栈区
/*
1、局部变量的存储空间,基本都是在栈区。局部变量在函数、循环、分支中定义。
2、栈区的内存由系统负责分配和回收,按照从高到低分配内存空间。在存储数据时,从低到高存储数据。
3、栈区里的内存,会在变量出了作用域后自动被系统回收。
4、栈的数据结构:先进后出(FILO),最先入栈的数据,会保存在栈底。(例如:手枪的弹夹)
5、栈区会存在数据稳定性的问题:在函数中返回栈的内存地址是不安全的,因为那块地址被系统回收了,随时会被其他数据占用。
*/
#pragma mark 静态区的特点
/*
1、全局变量,使用static修饰的变量(或局部变量)都存储在静态区
2、静态区的存储空间由系统分配和回收。
3、程序运行结束后,静态区的存储空间才会被回收。静态去变量的生命周期和程序的生命周期一样长。
4、静态区的变量只能初始化一次,在编译期进行初始化,在程序运行期,可以修改静态区变量的值。
5、静态变量如果没有设置初始值,默认设置为0。
6、如果变量是一个局部变量,使用static修饰之后,变量的作用域不会发生改变。 我们可以使用指针指向这块地址,这样在作用域之外就能正常访问它的值了。(因为静态区的变量,生命周期和程序运行的生命周期一样长)
*/
// int *p3 = NULL;
// for (int i = 0; i < 5; i++) {
// static int f = 34; // f从栈区挪到了静态区,在编译期,进行初始化,程序在运行时就不再执行这条代码
// f = 22; // 在运行期改值
// p3 = &f;
// }
#pragma mark 常量区(文字常量区)
/*
1、常量存储在常量区。如:常量字符串
2、常量区的内存由系统分配和回收
3、在程序执行结束后,常量区的内存空间才会被回收
4、常量区的数据只能被读取,不能被修改,修改会造成程序崩溃
*/
// char *p4 = "nice";
// p4[0] = 'a'; // 程序运行到这一行会崩溃,因为修改了常量区的内容
#pragma mark 代码区
/*
1、内存由系统分配和回收
2、程序运行结束后,由系统回收分配过的存储空间
3、代码区只能读取,不能修改
4、编程语言被翻译为机器指令后就存放在代码区
*/
#pragma mark 堆区
/*
1、由开发者负责分配和回收
2、如果只开辟,不回收的话,会造成内存泄露
3、堆区的内存如果程序员不回收的话,在程序执行结束后,系统会帮忙回收,但是如果程序员不能及时清理内存,那么程序运行期间可能会因为内存泄露造成堆内存被全部占用,程序无法继续运行。
*/
// 内存分配函数malloc
// void *malloc(size);
// void *:表示的是无类型指针,这种指针可以转化为任何类型指针(返回值可以被任何类型的指针保存)
// malloc:内存分配函数名
// size:函数参数,是一个无符号整形数,表示要在堆区中开辟的字节个数
// 这个函数的作用是:在堆区开辟指定size字节数的内存,并将内存首地址返回
// // 定义一个整形指针
// int *p = NULL;
// // 使用函数在堆区中开辟5个int类型的字节数
// p = malloc( sizeof(int) * 5 );
// // 将前四个字节里放入23
// *p = 23;
// // 下一个数字是44
// *(p+1) = 44;
// // 打印存进去的两个数,及其地址
// printf("%d\n%p\n%d\n%p\n", *p, p, *(p+1), p+1);
// 练习: 分配内存,用来存储float类型的数,12.34 并打印输出
// float *p = NULL;
// p = malloc( sizeof(float) );
//
// *p = 12.34;
// printf("%f\n%p\n", *p, p);
// 释放指针 ·
// int *p = malloc(sizeof(int));
// *p = 12;
// // 使用free函数,释放指针指向的内存空间
// free(p);
printf("*p = %d\n", *p);
//
// int *q = malloc(sizeof(int));
// *q = 45;
// printf("%p,%p\n", p, q);
// free(q);
// q = NULL; // 在指向的内存空间被释放后,应该将该指针指向0x0的位置,防止产生“野指针”的现象
free(q); // 此时造成了过度释放:对一个已经释放的区域再次释放
//
// int *p1 = NULL;
// p1 = malloc(sizeof(int) * 5); //sizeof(int) * 5 = 20
// for (int i = 0; i < 5; i++) {
// *p1 = i;
// p1++;
// }
//
// // 如果程序在这里就结束,就产生了内存泄露
//
// // 首先要将指针挪到这片地址空间起始地址
// for (int i = 0; i < 5; i++) {
// p1--;
// }
//
// // 在这里释放内存空间,字节数就是当初分配给p1指针访问的字节数(在这里是20个字节)
// free(p1);
//
// p1 = NULL;
// 练习1: 有一字符串,其中包含数字,提取其中的数字.要求动态分配内存保存 提示: 先计算出有几个数字,然后根据数字的个数来开辟空间.
// char string[] = "hey1ha2wu9";
// // 1.查找字符串中数字字符的个数
// // 1.1定义一个计数器,记录字符串中数字的个数
// int count = 0;
// // 1.2 使用循环来遍历字符串,找到数字字符
// for (int i = 0; i < strlen(string); i++) {
// // 1.2.1 判断当前字符是否是0~9之间的字符
// if ('0' <= string[i] && string[i] <= '9') {
// count++; // 符合判断条件,则让计数器加一
// }
// }
// // 打印验证
// printf("count = %d\n", count);
//
// // 2.开辟堆内存的空间,并将数字字符转换为整形数,存进去
// // 2.1 开辟堆内存中的空间
// int *p8 = malloc(sizeof(int) * count);
// // 2.2 使用循环将数字存进堆内存
// int num = 0; // 这个变量是给指针偏移使用的,在每找到一个数字后,就将(p+num)这个指针向后偏移的
// for (int i = 0; i < strlen(string); i++) {
// // 判断找出数字字符
// if ('0' <= string[i] && string[i] <= '9') {
// // 将数字字符转换为整形数
// int temp = string[i] - '0';
// printf("temp = %d\n", temp);
// // 把int类型的数字存进堆内存中
// *(p8 + num) = temp; //(p8 + num)
// num++;
//
// }
// }
//
// // 打印存进堆内存中的数据
// for (int i = 0; i < count; i++) {
// int a = *(p8 + i); // (p+i)是个新指针,不建议用(p++)
// printf("%d\n",a);
// }
//
// // 释放内存空间
// free(p8);
#pragma mark calloc和realloc
/*
calloc(<#size_t#>, <#size_t#>)
1、前一个参数表示n个单位,后一个参数表示一单位是几个字节(size)。开辟的总长度就是n*size个字节数
2、该函数会将开辟出来的内存空间清零
3、效率问题:由于会自动清零,所以calloc函数的执行效率低于malloc
*/
// // 在堆区开辟10个字节的空间,使用calloc
// char *str = NULL;
// str = calloc(2, 5);
// // 将“Hello”写入堆内存中
// strcpy(str, "hello");
// printf("%s\n", str);
// printf("%d\n", *(str + 8));
// free(str);
// str = NULL;
/*
realloc(<#void *#>, <#size_t#>)
1、第一个参数是指针,已有的内存空间的首地址(需要扩充内存字节数的空间的首地址)。第二个参数是:重新给这个指针指向的地址分配多少字节
2、如果原空间足够大,则在原空间基础上重新分配;如果原空间不够大,就会重新寻找一块连续的存储空间(能够分配新的字节个数),重新分配,之前保存在内存中的数据会被原样拷贝到新空间里。
*/
// int *p7 = malloc(10);
// printf("p7 = %p\n", p7);
// int *q7 = malloc(4);
//
// p7 = realloc(p7, 40);
// printf("p7 = %p\n", p7);
// free(p7); // 虽然p7的指向发生改变,但是在改变之前,系统就代替程序员将它原来指向的区域释放掉,在这里,我们只需要释放新指向的区域即可。
// free(q7);
#pragma mark 内存操作函数
/*
memset(<#void *#>, <#int#>, <#size_t#>)
三个参数,第一个是指针p,第二个是整型n,第三个长度size
这个函数的作用是:从给定的地址开始一直向后size个字节数,这个范围的内存的值被设置为n
*/
// 开辟5个int类型的空间,使用memset函数将这五个字节的空间初始化为3
// int *p = malloc(5 * sizeof(int));
// // 使用初始化函数设置初始值
// memset(p, 3, 5); // 最后一个参数表示的是初始化几个字节
// printf("*p = %d\n", *p);
// printf("*p = %d\n", p[0]);
/*
memcpy(<#void *#>, <#const void *#>, <#size_t#>)
1、前两个参数是指针类型,最后一个参数是整型,表示范围size
2、将第二个指针指向的地址开始,连续拷贝size个字节的内容到第一个指针指向的空间中
3、第一个指针指向的空间字节必须大于等于size
*/
// char *p1 = malloc(3);
// p1 = "ab";
//
// char *p2 = malloc(3);
// memcpy(p1, p2, 1);
// printf("%s\n", p1);
/*
* 我们可以使用memcpy函数来操作堆区的内存空间
*/
// char wrongName[] = "bj123";
// char rightName[] = "bj234";
// memcpy(wrongName, rightName, 3); // 将rightName指向的地址开始连续3个字节的内容拷贝到wrongName中。(这里操作的是栈区的内存)
// printf("%s\n", wrongName);
/*
memcmp(<#const void *#>, <#const void *#>, <#size_t#>)
字符串比较函数:参数是两个指针,从这两个指针指向的位置开始,一直比较size个字节,并将比较结果返回
*/
// int num1[3] = {1, 4, 3};
// int num2[3] = {1, 2, 3};
// int result = memcmp(num1, num2, 3); // 比较的是字节数,并不是元素的个数
// printf("result = %d\n", result);
return 0;
}