C语言基础:内存四区、数据类型、指针

mac2026-04-12  3

内存四区:堆、栈、全局区、代码区

内存四区

堆栈全局区 全局区常量区 代码区示意图(待补充)

1、堆

由程序员使用动态分配函数分配内存,需要头文件stdlib.h。

stdlib.h中的函数主要有

函数名函数原型功能返回值callocvoid *calloc (unsigned n,unsigned size);分配n个数据项的内存空间,每个数据项的大小为size个字节分配内存单元首地址;不成功则返回0freevoid free (void *p);释放p所指的内存区无mallocvoid *malloc (unsigned size);分配size个字节的存储空间分配内存空间的地址;如不成功返回0reallocvoid *realloc (void *p,unsigned size);把p所指的内存区的大小改为size个字节新分配内存空间的地址;如不成功返回0randint rand (void);产生0~32767的随机数返回一个随机数exitvoid exit (0);文件打开失败返回运行环境无

以上表格摘自书本,因此还有一些未去验证的疑问:

关于返回值,表格中的void*型函数,不成功时应当是NULL,0和NULL在计算机中或许等价(尚未想到验证方法);关于分配内存,若malloc分配的内存不是该数据类型的大小的整数倍,是否报错,或引起其他错误(未想到全面验证的方法);realloc函数,在改写p所指的内存区的大小后,返回新分配内存空间的地址,那么原内存空间是被覆盖还是被释放,是否会产生内存丢失(因为懒,暂未验证)。

堆区使用实例:

#include<stdio.h> #include<stdlib.h> int main() { //使用malloc函数,在堆区分配20个字节 //即分配5个int型变量大小的内存 //int *p = (int*)malloc(20); //关于内存分配 //避免出现上述提问2提及的可能出现的错误,建议按下列书写格式 int *p = (int*)malloc(5*sizeof(int)); //……对*p使用完后 free(p); //将堆释放掉 p = NULL; //清零p的指向,避免误判 return 0; }

2、栈

存放程序的局部变量,先入后出,由编译器自动分配内存,出栈的顺序基本就是程序执行的顺序

栈的实例:

#include<stdio.h> //假设栈开口向下 //则此时相当于在栈上分配了一个存放main函数的内存空间 int main() { //这两个变量,放在了栈区 int num1,num2; printf("请输入数字1:"); scanf("%d",&num1); printf("请输入数字2:"); scanf("%d",&num2); //以下面这条语句为例 printf("它们的和为:%d",sum(num1,num2)); //printf函数的返回值地址先入栈(反正是函数的某地址啦) //然后printf函数的参数 // "它们的和为:%d" 和 sum(num1,num2) 入栈 //再sum函数的返回值地址入栈 //随后,sum的参数 numOne 和 numTwo 入栈 //(具体到哪个参数先的话。。。应该是从右到左 //参考连接 https://www.cnblogs.com/xkfz007/archive/2012/03/27/2420158.html //随着函数依次运行 //numOne 、numTwo 出栈 //sum函数在栈上的内存空间析构 //sum返回值地址出栈 //printf函数的参数出栈 //printf函数在栈上的内存空间析构 //printf函数返回值地址出栈 return 0; } int sum(int numOne,int numTwo) { return (numOne+numTwo); }

3、全局区

这里的全局区实际是将“全局区”和"常量区“统称

若分开来看的话 全局区:存放全局变量、静态变量 常量区:存放常量、字符串 (PS:字面量,比如 int b = 123;在这句语句中,123就是字面量,在b入栈之前就存在,此时字面量应该在常量区)

全局区实例:

#include<stdio.h> int main() { //变量len在栈上,123在常量区 int len = 123; //然后在执行完该语句后,123放入len //同理,变量*str在栈上,"I an Chinese"在常量区 char *str = "I an Chinese"; //str存放"I an Chinese"的地址 return 0; }

4、代码区:存放代码

目前还没接触过需要操作代码区的地方,不清楚有什么需要了解的特性。

内存四区的示意图先咕着,待补充

数据类型

在C语言中,数据类型,可以说是不同内存大小的别名,我所知,其数据结构所定义的算法只有四则运算。 (PS:在数据结构的内容中,有这么一个说法,数据类型是已经实现的数据结构)

类型名字节位数值范围范围说明char18-128~127-27 ~ (27-1)unsigned char180~2550 ~ (28-1)short216-32768 ~ 32767-215 ~ (215-1)unsigned short2160 ~ 655350 ~ (216-1)int432-2147483648 ~ 2147483647-231 ~ (231-1)unsigned int4320 ~ 42949672950 ~ (232-1)float432-3.4x1038 ~ 3.4x10387位有效数字double864-1.7x10308 ~ 1.7x1030815位有效数字long long864未计算-263 ~ (263-1)unsigned long long864未计算0 ~ (264-1)long double1296未计算不清楚 以上,均可通过编译器验证;short是short int的缩写,同理long是long long int的缩写;这些基本数据类型,其差别是内存大小(float、double除外);float和double其数据存储形式与其它类型有差别(示意图待补充)。

指针:存放地址的数据类型

存放地址,通过类型,指定指针的步长

一级指针 步长 二级及多重指针 指针数组指向二维数组的指针(”行式“指针) const型指针指针与函数 指针作形参指针作返回值指向函数的指针 注意事项

一级指针

一级指针很好理解,就是在定义时多一个星号

//如下: int *num; //整型指针 指针本身在栈上占4字节内存 步长4字节 char *str; //字符型指针 指针本身在栈上占4字节内存 步长1字节 double *lf; //浮点型指针 指针本身在栈上占4字节内存 步长8字节 //计算指针所占内存的大小 printf("int型指针的所占内存的大小:%d\n",sizeof(num)); printf("char型指针的所占内存的大小:%d\n",sizeof(str)); printf("double型指针的所占内存的大小:%d\n",sizeof(lf)); //计算步长 printf("int型指针的步长:%d\n",sizeof(*num)); printf("char型指针的步长:%d\n",sizeof(*str)); printf("double型指针的步长:%d\n",sizeof(*lf));
步长

步长是指针的重要概念,与指针的加减运算相关 (PS:指针的加减运算,实质的指针指向的偏移,故没有乘除运算)【YY:除非某天出现向量指针甚至张量指针(啊,真是让人头秃的假想)】

//理解步长 int *num; char *str; double *lf; //以下仅为假设示例 //除非清楚地址(内存标号)所指向的内存内容,不然请勿模仿 //初始化 指向同一个地址 num = 0xaaaaa; str = 0xaaaaa; lf = 0xaaaaa; //执行+1操作 num++; str++; lf++; //用十六进制显示 printf("num存放的地址值:%x\n",num); printf("str存放的地址值:%x\n",str); printf("lf存放的地址值:%x\n",lf); 输出: num存放的地址值:0xaaaae //比原来多4字节 str存放的地址值:0xaaaab //比原来多1字节 lf存放的地址值:0xaaab4 //比原来多8字节

二级及多重指针

从指针来说,无论是几级指针,都是存放地址因为指针的星号操作,所以n级指针,存放(n-1)级指针的地址还有步长的区别,我所知,这一点只在指向多维数组的指针中体现 //理解多级指针 char ***str_T; char **str_O; char *str = "I am Chinese"; //指向一个字符串 str_O = &str; //指向str str_T = &str_O;//指向str_O str_O = str_O+1;//偏移str_O的指向(一般,此操作无意义) *str_O = *str_O+1; //使str存储的地址值加一个步长 **str_O = **str_O+1;//报错 常量区的内容无法更改 printf("打印字符串str:%s\n",str); str_T = str_T+1;//偏移str_T的指向(一般,此操作无意义) *str_T = *str_T+1;//偏移str_O的指向(一般,此操作无意义) **str_T = **str_T+1; //使str存储的地址值加一个步长 ***str_O = ***str_O+1;//报错 常量区的内容无法更改 printf("打印字符串str:%s\n",str); 输出: 打印字符串str: am Chinese 打印字符串str:am Chinese 第二个比第一个少输出一个空格 因为第一个只移一个步长,第二个共移了两个步长
指针数组

顾名思义,以数组的形式,定义多个指针

//指针数组 //定义了存放地址的数组 int *p[5]; //有5个指针元素
指向二维数组的指针(“行式”指针)

指向多维数组的指针可以是普通的指针,也可以是“行式”指针 此处只对”行式“指针进行说明

//理解”行式“指针 //定义一个3x4的二维数组 int numlen[3][4]={1,3,5,7, 9,11,13,15, 17,19,21,23}; int(*num)[4]; //定义一个”行式“指针 步长为4xsizeof(int)字节 p = a; //指向数组a //以打印元素的方式验证 printf("num指向的元素:%d\n",*(*num)); printf("num+1指向的元素:%d\n",*(*(num+1))); //以打印地址的方式验证 printf("num的地址:%d\n",num); printf("num+1的地址:%d\n",num+1); //注意 下列书写依然是打印地址 printf("仍是存放在num的地址:%d\n",*num); printf("仍是存放在num+1的地址:%d\n",*(num+1)); //打印行内的元素 //打印元素a[0][1] printf("打印num指向的行内元素:%d\n",*(*num+1)); //打印元素a[1][1] printf("打印num+1指向的行内元素:%d\n",*(*(num+1)+1)); 输出: 1 9 地址根据系统变化,但二者之间,地址值相差16 同上方的地址一样,二者值同样相差16 3 11 从打印行内的元素的方式,可以看出该案例中的“行式”指针是二级指针“行式”指针,可读性相对较差,不易维护,很少使用“行式”指针,几乎与二维数组共同出现三维数组,可以是“页式”指针

const型指针

const型指针有两种

//可进行遍历的只读指针 //可以修改p的值,但不能用*p修改a的值 const int *p = &a; //不可进行遍历的标志指针 //不能修改p的值 int * const p = &a; //与数组首地址作用相同 //存储的地址不会变化,可作为函数形参,标识地址

指针与函数

指针除了内存操作外,可以说是专服务于函数

指针作形参

指针作形参,就是作为函数的参数,其目的,大多都是为函数提供多个返回值的 (PS:指针忌指向临时变量,即在指针使用的过程中,勿指向已析构或即将析构的变量 此点将在注意事项中作示例)

函数作形参实例:

//指针作形参 #include<stdio.h> //函数声明 void swap(int *pt1,int *pt2); void exchange(int *p1,int *p2,int *p3); int main() { int num1,num2,num3; //对需要输入的数据进行必要的说明 printf("请输入num1:"); scanf("%d",&num1); printf("\n"); printf("请输入num2:"); scanf("%d",&num1); printf("\n"); printf("请输入num3:"); scanf("%d",&num1); printf("\n"); //调用排序函数 将变量的地址传递给函数的形参指针 exchange(&num1,&num2,&num3); printf("从大到小排序后:%d,%d,%d\n",num1,num2,num3); return 0; } void exchange(int *p1,int *p2,int *p3) { //在判断为真后,调用换值函数 交换变量中的值 if(*p1 < *p2)swap(p1,p2); if(*p1 < *p3)swap(p1,p3); if(*p2 < *p3)swap(p2,p3); } void swap(int *pt1,int *pt2) { int temp; temp = *pt1; *pt1 = *pt2; *pt2 = temp; }
指针作返回值

所谓指针作返回值,就是定义函数时,使用指针类型

//如下 //定义函数的类型,其实是定义函数返回值的类型 int* twoSum(int* nums, int numsSize, int target) { static int a[2]={0}; for (int i = 0; i < numsSize - 1; i++) { for (int j = i+1; j < numsSize; j++) { if (nums[i] + nums[j] == target) { a[0] = i; a[1] = j; return a; } } } return 0; }
指向函数的指针

指向函数的指针,其实就是通过指针调用函数,就我的学习经历来说,使用不多

指针调用函数实例:

#include<stdio.h> int main() { //定义三个存放数据的变量 int num1,num2,NumMax; //定义一个可以指向函数的指针 int (*p)(intint; //对需要输入的数据进行必要的说明 printf("请输入num1:"); scanf("%d",num1); printf("\n"); printf("请输入num2:"); scanf("%d",num2); printf("\n"); //指针指向函数 p = max; //用指针调用函数 NumMax = (*p)(num1,num2); //输出结果 printf("num1 = %d\tnum2 = %d\t NumMax = %d\n",num1,num2,NumMax); return 0; } //定义一个返回两数中最大数的函数 int max(int x,int y) { return x > y ? x : y; }

注意事项

想要安全地使用指针,就必须明确指针指向的内存空间信息 如: 这块内存空间的生命周期有多长 这块内存空间能否被操作 内存空间不再使用时,是否已释放 在释放内存空间后,指针是否已清零

错误的函数示例:

//示例 错误函数 int* twoSum(int* nums, int numsSize, int target) { int a[2]={0}; for (int i = 0; i < numsSize - 1; i++) { for (int j = i+1; j < numsSize; j++) { if (nums[i] + nums[j] == target) { a[0] = i; a[1] = j; //返回值有误 a是该函数在栈上临时分配的内存 //在函数调用结束后,会被析构 return a; } } } return 0; }

错误的调用:

int numlen[]= {2, 7, 11, 15}; int *result; int size = sizeof(numlen)/sizeof(numlen[0]); twoSum(numlen,size,9,result); int* twoSum(int* nums, int numsSize, int target,int *out) { int a[2]={0}; for (int i = 0; i < numsSize - 1; i++) { for (int j = i+1; j < numsSize; j++) { if (nums[i] + nums[j] == target) { a[0] = i; a[1] = j; //值传递有误 a是本函数中定义的临时变量 //函数调用完毕后,会被析构 //无法通过指针 将内容传递出去 out = a; } } } return 0; }

正确函数书写:

int numlen[]= {2, 7, 11, 15}; int *result; int size = sizeof(numlen)/sizeof(numlen[0]); twoSum(numlen,size,9,result); int* twoSum(int* nums, int numsSize, int target,int *out) { //使用静态变量 其内存位置在全局区 static int a[2]={0}; for (int i = 0; i < numsSize - 1; i++) { for (int j = i+1; j < numsSize; j++) { if (nums[i] + nums[j] == target) { a[0] = i; a[1] = j; out = a; } } } return 0; }

指针越界:

char *str; char strlen[4] = {'I',' ','a','m'}; //指针指向字符数组 str = strlen; //错误操作 字符数组不是字符串 缺少’\0‘ //因此会越界输出 printf("%s",str); //其它越界 str = strlen[3]; //指向数组外的未知内存 str++; //越界输出 printf("%s",str);

操作常量区:

char *str = "I am Chinese"; //该指针指向常量区的字符串 free(str); //错误操作 常量区无法操作

内存丢失:

char *str1 = "I am Chinese"; //在堆上分配100字节的内存 char *str2 = (char *)malloc(100); //错误操作 str2指针指向了常量区字符串 str2 = str1; //在堆上分配的100字节内存丢失

指针不清零:

char *str1 = "I am Chinese"; //在堆上分配100字节的内存 char *str2 = (char *)malloc(100); //假设使用完毕 进行释放 if(str2 != NULL) { free(str2); } //计划重新使用 if(str2 !=NULL) { //错误操作 str2指向的内存已被释放 strcpy(str2,str1); } //因此 释放指针指向的内存后,指针应当复位清零 if(str2 != NULL) { free(str2); str = NULL; } 指针忌指向临时变量 1. 忌指针型返回值指向该函数内的临时变量 2. 忌外部指针指向已调用结束的函数内的临时变量操作不可操作的内存区内存丢失和指针清零指针越界
最新回复(0)