标签:10 include 管理 int lesson8 初始化 内存 printf 全局变量
8. 内存管理
8.1 作用域
C语言变量的作用域分为:
- 代码块的作用域
- 函数作用域
- 文件作用域
#include <stdio.h>
void fun1(int a)
{
int b = 20;
}
int main()
{
//定义变量 局部变量:在函数内部的变量,可以用auto来修饰,与全局变量作区分
//作用域:在函数内部
//声明周期:从创建到函数结束
auto int a = 1;
printf("%d\n", a);
return 0;
}
函数内部不重新定义就会使用全局变量,可以直接修改全局变量的值
全局变量:在函数外定义,可被本文件及其他文件中的函数使用,若其他文件中的函数调用此变量,须用extern声明
不同文件中的全局变量名不可以重复,但可以和局部变量重名
#include <stdio.h>
int a = 10;//全局变量 在函数外部定义的变量,储存在数据区
//全局变量可以和局部变量重名,使用的时候采用就进原则
//作用域:整个项目中所有的文件,如果在其他的文件中使用,需要申明
//声明周期是从程序创建到程序销毁。
void fun02(int a)
//在自己调试的时候发现,不传参就可以直接改变全局变量的值,在返回main之后的a被改成了fun02中的值
{
a = 100;
printf("%d\n", a);
}
int main()
{
int a = 123;
//匿名内部类
{
int a = 456;
printf("%d\n", a);
}
printf("%d\n", a);
fun02(a);
printf("%d\n", a);
return 0;
}
静态局部变量:
#include <stdio.h>
void fun04()
{
int b = 10;
b++;
printf("%d\t", b);
}
void fun05()
{
static int b = 10;
//静态局部变量只会初始化一次,可以多次赋值
//在数据区进行存储,作用域、:只能在函数内部使用,生命周期:程序创建到销毁
b++;
printf("%d\t", b);
}
int main()
{
//static int b = 10;//静态局部变量
//printf("%d\n", b);
for (size_t i = 0; i < 10; i++)
{
fun04();
}
putchar(10);
for (size_t i = 0; i < 10; i++)
{
fun05();
}
return 0;
}
输出:
11 11 11 11 11 11 11 11 11 11
11 12 13 14 15 16 17 18 19 20
静态全局变量:
#include <stdio.h>
static int c = 10;
//静态全局变量
//作用域:可以在本文件中使用,不可以在其他文件中使用
//声明周期:程序创建到销毁
void fun06()
{
c = 20;
printf("%d\n", c);
}
int main()
{
printf("%d\n", c);
fun06();
return 0;
}
10
20
全局函数和静态函数
在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。
对于不同文件中的staitc函数名字可以相同。
#include <stdio.h>
static void fun07()
{
printf("hello world");
}
int main()
{
fun07();
}
输出:hello world
类型 | 作用域 | 生命周期 |
---|---|---|
auto变量 | 一对{}内 | 当前函数 |
static局部变量 | 一对{}内 | 整个程序运行期 |
extern变量 | 整个程序 | 整个程序运行期 |
static全局变量 | 当前文件 | 整个程序运行期 |
extern函数 | 整个程序 | 整个程序运行期 |
static函数 | 当前文件 | 整个程序运行期 |
register变量 | 一对{}内 | 当前函数 |
全局变量 | 整个程序 | 整个程序运行期 |
8.2 内存布局
安全的常量:存储区域为数据区常量区
#include <stdio.h>
//未初始化全局变量
int a1;
//初始化全局变量
int b1 = 10;
//未初始化静态全局变量
static int c1;
//初始化的静态全局变量
static int d1 = 10;
int main()
{
int e1 = 10;
static int f1;
static int h1 = 10;
char* p = "helloworld";//字符串常量
int arr[] = { 1,2,3,4 };
int* pp = arr;
printf("未初始化的全局变量:%p\n", &a1);
printf("初始化的全局变量:%p\n", &b1);
printf("未初始化的静态全局变量:%p\n", &c1);
printf("初始化的静态全局变量:%p\n", &d1);
printf("局部变量:%p\n", &e1);
printf("未初始化的静态局部变量:%p\n", &f1);
printf("初始化的静态局部变量:%p\n", &h1);
printf("字符串常量:%p\n", p);
printf("数组:%p\n", arr);
printf("指针变量:%p\n", pp);
printf("指针地址:%p\n",&pp);
return 0;
}
未初始化的全局变量:00B0A164
初始化的全局变量:00B0A004
未初始化的静态全局变量:00B0A158
初始化的静态全局变量:00B0A008
局部变量:00C0F728
未初始化的静态局部变量:00B0A15C
初始化的静态局部变量:00B0A00C
字符串常量:00B07B44
数组:00C0F704
指针变量:00C0F704
指针地址:00C0F6F8
C代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。
通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把data和bss合起来叫做静态区或全局区)。
代码区
存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。
全局初始化数据区/静态数据区(data段)
该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
未初始化数据区(又叫 bss 区)
存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。
程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。
栈区
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
堆区(heap)
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
存储是从高地址往低地址进行存储的
#include <stdio.h>
int main()
{
//栈区大小
int arr[820000] = { 0 };
return 0;
}
调试直接报错:0x008E2477 处有未经处理的异常(在 day08.exe 中): 0xC00000FD: Stack overflow (参数: 0x00000000, 0x00E82000)。
堆区最大内存就是1M
堆区内存分配和释放
-
malloc() : #include <stdlib.h>
形式:void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
-
free() :
库:#include <stdlib.h>
形式:void free(void *ptr);功能:放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。对同一内存空间多次释放会出错。
#include <stdio.h>
#include<stdlib.h>
int main()
{
//栈区大小
//int arr[820000] = { 0 };
//开辟堆空间存储数据
int* p = (int*)malloc(sizeof(int));
printf("%p\n", p);
//使用堆空间
*p = 123;
printf("%d\n", *p);
//释放堆空间
free(p);
printf("%p\n", p);
//释放完p之后变成野指针,操作它可能会报错,也可能不报
*p = 456;
printf("%d\n", *p);
p = NULL;
return 0;
}
输出:
00F966C8
123
00F966C8
456
开辟和释放的指针必须是同一个,如果在过程中改变了指针的值,必须要改回去才能释放
内存操作函数
-
memset()
include <string.h>
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入.
#include <stdio.h> #include<stdlib.h> #include<string.h> int main() { int* p = (int*)malloc(sizeof(int) * 10);//开辟40个空间,存储10个整形变量 //memset()可以重置内存空间的值 memset(p, 0, 40);//单位是字节 for (int i = 0; i < 10; i++) { printf("%d\n", p[i]); } free(p); return 0; }
0
0
0
0
0
0
0
0
0
0
四位存储一个数,八位是一个字节,int一共四个字节,如果memset把所有字节变为1,则存储的数就是01010101
-
memcpy()
include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。memcpy和strcpy的区别:内存拷贝的内容和字节有关,与拷贝内容无关,
int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = (int*)malloc(sizeof(int) * 10); memcpy(p, arr, 40);//内存拷贝 for (int i = 0; i < 10; i++) { printf("%d\t", p[i]); } free(p); return 0; }
输出:1 2 3 4 5 6 7 8 9 10
int main() { int arr[]= { 1,2,3,4,5,6,7,8,9,10 }; memcpy(&arr[5], &arr[3], 20); for (int i = 0; i < 10; i++) { printf("%d\t", arr[i]); } return 0; }
输出1 2 3 4 5 4 5 6 7 8
dest和src所指的内存空间不可重叠,可能会导致程序报错
-
memmove()
memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。
- memcmp()
include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节
返回值: 相等:=0 大于:>0 小于:<0
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[] = { 1,2,3,4,5 };
int value = memcmp(arr1, arr2, 20);
int value2=memcmp(arr1, arr2, 28);
printf("%d\n", value);
printf("%d\n", value2);
char arr3[] = "hello\0 world";
char arr4[] = "hello\0 world";
int value3 = memcmp(arr3, arr4, 10);//strcmp只能比较\0之前的内容,memcmp可以比较后面的
printf("%d\n", value3);
return 0;
}
输出:
0
-1
0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
//数组下标越界
//char ch[] = "hello world";
char* p = (char*)malloc(sizeof(char) * 11);
strcpy(p, "hello world");
printf("%s\n", p);
//free(p); 这里释放程序会卡死,因为创建了11个字节,但是存进去了12个
return 0;
}
输出:hello world
int main()
{
int* p = (int*)malloc(0);//类似野指针,没有开辟空间,操作不了
printf("%p\n", p);
*p = 100;
printf("%d\n", *p);
//free(p); 会因为越界卡死
return 0;
}
输出:005D6808
100
开辟内存和空间要对应,不能越界,所以平时写的时候尽量写成malloc(sizeof(xxx)*xx)的形式
int main()
{
int* p = (int*)malloc(sizeof(int) * 11);
free(p);//堆空间不允许多次释放
p = NULL;//释放空指针是没问题的
free(p);//空指针允许多次释放
free(p);
free(p);
free(p);
free(p);
free(p);
return 0;
}
输出空的
void fun08(int *p)
{
p = (int*)malloc(sizeof(int) * 10);
}
int main()
{
int* p = NULL;
fun08(p);
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
free(p);
return 0;
}
这样写传递过去的p和fun08函数里面p是同级的,使用完以后就会被销毁,相当于值传递。后面操纵的都是空指针,会报错
void fun08(int **p)
{
*p = (int*)malloc(sizeof(int) * 10);
printf("形参:%p\n", p);
}
int* fun09()
{
int* p = (int*)malloc(sizeof(int) * 10);
return p;
}
int main()
{
int* p = NULL;
fun08(&p);//地址传递
printf("实参:%p\n", p);
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d\t", p[i]);
}
free(p);
return 0;
}
输出:
形参:0095FB08
实参:00BA6788
0 1 2 3 4 5 6 7 8 9
标签:10,include,管理,int,lesson8,初始化,内存,printf,全局变量 来源: https://www.cnblogs.com/hjhstudycs/p/15689707.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。