C语言指针的相关知识(2023.09.09)

学习进步、培养兴趣、发掘潜力。

十、指针和动态内存

1、堆与栈

在一个典型的架构中,分配给应用程序的内存可以被分为4个区段,如下图所示:

来看下面这段代码:

#include<stdio.h>
int total;
int Square(int x)
{
	return x*x;
}

int SquareOfSum(int x,int y)
{
	int z=Square(x+y);
	return z;
}
int main()
{
	int a=4,b=8;
	total=SquareOfSum(a,b);
	printf("output = %d",total);
}

当程序执行时,系统的栈上的内存分配如下图:

                                                        

在任何时候,都是栈顶的函数在执行,而其他的函数会暂停,等待上面的函数返回一些值然后再恢复执行。而一旦函数结束执行,其之前占用的栈上内存也将被清除。

注意:如果栈的增长超出了预留的内存空间,导致最后耗尽了栈空间(也被称为栈溢出),那么程序将会崩溃。(例如无穷递归

与栈不同,应用程序的堆并不是固定的,大小可变、也没有特定规则来分配和销毁相应的内存。

为了在C中使用动态内存,我们需要知道4个函数:malloc、calloc、realloc、free。

为了在C++中使用动态内存,我们需要知道两个操作符:new、delete

malloc返回的是void型指针,实际上,malloc函数所做的工作就是:从堆上找到空闲的内存、预留内存并通过指针返回。需要注意的是,当我们调用完malloc函数后,其仍在堆上占有内存,所以我们需要使用free来释放内存。(与栈不同,栈上的内存在程序执行完后会自动释放

如果malloc找不到空闲的内存块、将返回NULL。

2、malloc calloc realloc free

malloc:malloc是在进行动态内存分配时最常使用的库函数之一,malloc的函数定义为:void* malloc(size_t size);参数是内存块的字节数大小,size_t指的是正整数,类似于unsigned int。因为malloc返回的是一个void指针,所以我们在使用内存前,首先需要进行指针类型转换。

calloc:calloc的函数定义为:void* calloc(size_t num,size_t size);calloc同样返回一个void指针,但是calloc接收的是两个参数,第一个参数是特定类型元素的数量、第二个参数是类型的大小。malloc与calloc有一些不同之处,当malloc分配完内存后,并不会对其进行初始化,但是如果使用的是calloc,会对其进行初始化为0。

下面这个例程很好的解释了calloc的用法:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int n;
	printf("Enter size of array\n");
	scanf("%d",&n);
	int *A =(int*)calloc(n,sizeof(int));
	for(int i=0;i<n;i++)
	{
	A[i] = i+1;
	}
}

realloc:如果拥有一块动态分配的内存,当想修改内存块的大小时,可以使用realloc。realloc函数定义为:void* realloc(void* ptr,size_t size);当传入恰当的参数时,realloc可以作为free或者malloc的替代品。

free:任何分配了的动态内存在程序结束之前都会一直存在。为了释放内存,可以使用函数free。

3、内存泄露

概念:内存泄露指的是动态申请了内存后,但是使用完之后却没有释放内存。

十一、函数返回指针

首先来看下面这段代码:

#include<stdio.h>
#include<stdlib.h>
int Add(int* a,int* b)//a和b是整型指针
{
	printf("Add函数中a的地址 = %d\n",&a);
	printf("Add函数中a的值(main函数中a的地址) = %d\n",a);
	printf("存储在Add函数中a地址内的值 = %d\n",*a);
	int c= (*a)+(*b);
	return c;
}
int main()
{
	int a=2,b=4;
	printf("main函数中a的地址= %d\n",&a);
	int c=Add(&a,&b);
	printf("Sum = %d\n",c);
}

可以看到,当程序运行后,Add函数中a的值(main函数中a的地址)与main函数中a的地址的结果是一模一样的。

接下来看下面这段程序:

#include<stdio.h>
#include<stdlib.h>
void PrintHelloWorld()
{
	printf("Hello World\n");
}

int *Add(int* a,int* b)
{
	int c = (*a)+(*b);
	return &c;
}

int main()
{
	int a=2,b=4;
	int* ptr = Add(&a,&b);
	PrintHelloWorld();
	printf("Sum = %d\n",*ptr);
}

仅仅是加上了一个打印Hello World的函数,为什么就无法得到想要的结果呢?

***让我们来分析一下:当发生函数调用时,栈上将会分配一些内存,这些内存就被称为那个函数的“栈帧”,当程序执行时,首先main函数被调用,栈上分配一个供main函数运行的栈帧。main函数的局部变量放在这个栈帧中。当main函数执行到调用Add函数时,main函数将会暂停,而在栈上会分配Add函数执行所需要的内存,而之前说过,在任何时间,在执行的函数都会是栈顶的那个函数,所以main函数将等待Add函数完成返回。C计算*a+*b的值,最后,将C的地址写入ptr。但是,当Add函数调用完成后,系统将擦除Add在栈上所占的内存空间,而这时再调用PHW函数时,之前的ptr内地址可能已经分配给了PHW函数使用,这就导致了数据丢失。

从栈底向上传输参数是可行的,但是从栈顶向下传输参数时,数据可能会丢失。

那么,在什么情况下,我们我们想要从函数返回一个指针呢?

当我们在堆上有一个内存地址,或者在全局区有一个变量时,我们就可以安全返回它们的地址。因为堆上分配的内存需要显式释放(这点与栈不同,栈上内存是自动释放的)。而全局区的任何东西的生命周期是整个程序的执行期间。

十二、函数指针

函数指针,顾名思义就是用来存储函数的地址的指针。通过应用函数指针,可以解引用和执行函数。

首先,我们需要理解程序的定义:程序可以认为是一组顺序的计算机指令集合,我们可以使用C语言进行编程,但是在计算机的底层,他们最终都将以二进制的格式执行,任何需要被执行的程序都将被编码为2进制格式。所以实际上,当我们编写程序时,我们使用C或C++作为高层语言,再讲这些源代码作为编译器的输入,编译器进而编译出机器代码。

.c文件->.exe文件(可执行文件)

#include<stdio.h>
int Add(int a,int b)
{
	return a+b;
}
int main()
{
	int c;
	int(*p)(int,int);//函数指针中的参数要和所指向的这个函数的参数类型一致。
	p=&Add;
	c=(*p)(2,3);
	printf("%d",c);
}

十三、函数指针的使用案例

回调函数:一个函数的引用传给另外一个函数时,那个函数就被称为回调函数。下面的A就是一个回调函数。

#include<stdio.h>
void A()
{
	printf("Hello");
}

void B(void (*ptr)())
{
	ptr();
}

int main()
{
	B(A);//A是一个回调函数
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值