C语言目录:
9.1 进程空间
程序(静态):经编译后的可执行文件可以被多次执行
进程(动态):程序在内存中的运行状态为进程
9.1.1 栈内存(stack)
栈中存放任意类型变量,但必须是 auto
类型修饰符的局部变量
随用随开,用完即销
内存的分配和销毁由系统自动完成,不需要人工干预
- 栈内存的分配:从高地址到低地址,为变量分配的内容由
数据类型
和空间首地址
标记
栈有最大存储空间限制,超出则引起栈溢出
- 如局部变量过多,过大或递归层数太多
#include <stdio.h>
int main(){
int a = 1;
int b = 2;
printf("&a = %p\n", &a); // &a = 0ABC0FFC
printf("&b = %p\n", &b); // &b = 0ABC0FF8
return 0;
}
9.1.2 堆内存(heap)
堆内存可以存放任意类型的数据,但需要自己申请与释放
int *p = (int *)malloc(sizeof(int)*1024*1024);
//不一定会崩溃
连续两次申请的内存可能不连续,但一定是从小地址开始分配内存
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p1 = (int *)malloc(4);
*p1 = 1;
int *p2 = (int *)malloc(4);
*p2 = 2;
printf("p1 = %p\n", p1); //p1 = 00000227639268C0
printf("p2 = %p\n", p2); //p2 = 0000022763926900
return 0;
}
9.2 变量内存分析
9.2.1 变量存储过程
一个变量所占用的存储空间,和 定义变量时声明的类型 以及 编译环境 有关
类型 | 32位编译器 | 64位编译器 |
---|---|---|
char | 1 | 1 |
int | 4 | 4 |
float | 4 | 4 |
double | 8 | 8 |
short | 2 | 2 |
long | 4 | 8 |
long long | 8 | 8 |
void* | 4 | 8 |
a. 变量存储的过程
- 根据定义变量时声明的
变量类型
和当前编译环境
确定需要开辟多大存储空间 - 在内存中开辟一块存储空间,开辟时从内存地址大的开始开辟,将最小的地址返回给变量
- 将数据保存到已经开辟好的内存空间内
变量名:变量的首地址,是变量所占存储空间字节地址(最小的那个地址 )
小端模式:数据的高字节高地址
9.2.2 内存读写操作
CPU在运行时要明确三件事:
-
存储单元的地址(地址信息)
通过地址总线找到存储单元的地址
-
对部件的操作(读或写,控制信息)
通过控制总线发出内存读写指令
-
数据(数据信息)
通过数据总线传输需要读写的数据
a. 写入过程
- CPU 通过地址线将找到地址为 FFFFFFFC 的内存空间
- CPU 通过控制线发出内存写入命令,选中存储器芯片,并通知它,要其写入数据
- CPU 通过数据线将数据 12 送入内存 FFFFFFFC 单元中
b. 读入过程
- CPU 通过地址线将找到地址为 FFFF FFFC 的内存
- CPU 通过控制线发出内存读取命令,选中存储器芯片,并通知它,将要从中读取数据
- 存储器将 FFFFFFFC 号单元中的数据 12 通过数据线送入 CPU寄存器中
9.3 数据存储区
执行C语言程序,可用的存储区有
-
程序区
-
静态存储区:编译时分配内存;包括
static,extern
-
动态存储区:运行时分配内存;包括
static,auto
9.3.1 static
静态局部变量
- 仅在本函数中使用,调用后不清零
- 只能赋一次初值,若不初始化 自动赋0或空字符
- 可使用静态局部变量跟踪调用次数
静态全局变量
- 仅限在定义的文件中使用,可使不同源文件中的静态全局变量独立
9.3.2 extern
在函数外部引用变量使用 extern
,扩充已定义的全局变量作用域
一个C语言程序包含多个源程序,若使用同一外部变量,在一个源文件中定义,在另一个中用 extern
说明是外部变量。引用外部函数时,被引用函数需添加 extern
9.4 动态分配内存
stdlib.h
9.4.1 malloc
void *malloc(size_t size);
:在内存中申请一块连续的堆内存空间并返回,所申请的空间并未初始化
-
size :表示要申请的字符数
-
返回值:
成功,返回非空指针,指向申请的空间
失败,返回
NULL
初始化方法:memset(指针, 初始值, 待初始化字节数)
字节初始化
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p = (int *)malloc(sizeof(int));
printf("p = %d\n", *p); // 垃圾值
memset(p, 0, sizeof(int)); // 初始化为0
printf("p = %d\n", *p);
return 0;
}
9.4.2 free
void free(void *p);
: 释放通过 malloc()
申请的堆内存空间,所以 malloc()
和 free()
总是成对出现
#include <stdio.h>
#include <stdlib.h>
int main(){
int *p = (int *)malloc(sizeof(int));
printf("p = %d\n", *p); // 垃圾值
memset(p, 0, sizeof(int)); // 初始化为0
printf("p = %d\n", *p);
free(p);
return 0;
}
9.4.3 calloc
void *calloc(size_t n,size_t size);
:在堆内存中申请 n
块长度为 size
的空间,申请的空间自动初始化为0
- 成功,返回非空指针指向申请的空间
- 失败,返回
NULL
#include <stdio.h>
#include <stdlib.h>
int main(){
// 申请3块4个字节的存储空间
int *p = calloc(3, sizeof(int));
p[0] = 1;
p[1] = 3;
p[2] = 5;
printf("p[0] = %d\n", p[0]);
printf("p[1] = %d\n", p[1]);
printf("p[2] = %d\n", p[2]);
free(p);
return 0;
}
9.4.4 realloc
void *realloc(void *p,size_t size);
:扩充或缩小原有内存的大小
-
参数
p
:表示待扩容的空间指针size
:表示扩容后内存的大小 -
返回
成功:返回非空指针指向申请的空间
失败:返回
NULL
注意:
-
当
p == NULL
时,realloc(NULL,sizeof(int))
等同于malloc(sizeof(int))
-
返回的指针,可能与
p
原本的值相同,也可能不同相同:则原空间后后续空间充足
不同:原空间后续内存空间不足,重新申请新的连续空间后,将元数据拷贝到新空间,原有空间会被自动释放
#include <stdio.h>
#include <stdlib.h>
int main(){
// 申请4个字节存储空间
int *p = malloc(sizeof(int));
printf("p = %p\n", p);
//扩容
p = realloc(p, sizeof(int) * 2);
printf("p = %p\n", p);
//使用申请好的空间
*p = 666;
printf("*p = %d\n", *p);
// 释放空间
free(p);
return 0;
}
9.4.5 应用——动态链表
a. 链表与静态链表
链表:将零碎的内存空间组织为一组可用的内存空间
- 使用
malloc
申请内存空间时,若没有足够大的连续内存空间,则申请失败
静态链表:链表的长度固定,链表的结点固定
# include<stdio.h>
# include<stdlib.h>
//链表结点
typedef struct node{
int data;
struct node *next;
}Node;
int main(){
Node a,b,c;
Node *head = &a;
a.data = 1;
b.data = 2;
c.data = 3;
a.next = &b;
b.next = &c;
c.next = NULL;
while(head != NULL){
int cur = head->data;
printf("%d\n",cur);
head = head->next;
}
return 0;
}
空链表
头指针带了一个空链表结点,空链表结点中的 next
指向 NULL
静态链表数据存储在栈上,而栈的存储空间有限。所以要实现链表存储空间的动态分配,需要申请堆里的存储空间
b. 动态链表的创建
头插法
涉及数据结构的线性表,可自行查看:https://auspicetian.life/posts/3975030669/
# include<stdio.h>
# include<stdlib.h>
//定义结点
typedef struct node {
int data;
struct node *next;
}Node;
//创建链表
Node *createList();
//按顺序打印链表中数据
void printNodeList();
int main(){
Node *head = createList();
printNodeList();
return 0;
}
Node *createList(){
//创建头结点
Node *head = (Node *)malloc(sizeof(Node));
if(head == NULL)
return Null;
head->next = NULL;
//从键盘接收数据
//-1表示输入结束
printf("请逆序输入结点数据:\n");
int data;
scanf("%d",&data);
while(data != -1){
//创建新结点
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = data;
cur->next = head->next;
head->next = cur;
printf("请逆序输入结点数据:\n");
scanf("%d",&data);
}
return head;
}
void printNodeList(Node *node){
Node *head = node->next;
while(head != NULL){
int data = head->data;
printf("%d\n",data);
head = head->next;
}
}
尾插法
# include<stdio.h>
# include<stdlib.h>
//定义结点
typedef struct node {
int data;
struct node *next;
}Node;
//创建链表
Node *createList();
//按顺序打印链表中数据
void printNodeList();
int main(){
Node *head = createList();
printNodeList();
return 0;
}
Node *createList(){
Node *head = (Node *)malloc(sizeof(Node));
if(head == NULL)
return NULL;
head->next = NULL;
//从键盘接收数据
//-1表示输入结束
printf("请顺序输入结点数据:\n");
int data;
scanf("%d",&data);
Node *pre = head;
while(data != -1){
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = data;
pre->next = cur;
cur->next = NULL;
pre = cur;
scanf("%d",&data);
return head;
}
}
void printNodeList(Node *node){
Node *head = node->next;
while(head != NULL){
int data = head->data;
printf("%d\n",data);
head = head->next;
}
}
c. 插入
void preInsertNode(Node *head,int data){
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = data;
cur->next = head->next;
head->next = cur;
}
void tailInsertNode(Node *head,int data){
Node *pre = head;
while(pre != NULL && pre->next != NULL)
pre = pre->next;
Node *cur = (Node *)malloc(sizeof(Node));
cur->data = data;
cur->next = NULL;
pre->next = cur;
}
d. 销毁
void destoryList(Node *head){
Node *cur = NULL;
while(head != NULL){
cur = head->next;
free(head);
head = cur;
}
}