C++ 指针 1

本文详细介绍了C++中的指针概念,包括如何声明、使用指针在栈和堆上的内存分配,重点讲解了malloc、calloc和realloc动态内存分配函数,以及如何处理和避免悬挂指针问题。
摘要由CSDN通过智能技术生成

指针是C++中一种重要的数据类型,它存储了内存地址。指针可以用来直接访问内存中的数据,也可以用来在函数之间传递数据或动态分配内存。在C++中,要声明一个指针,需要指定指针所指向的数据类型,并使用星号(*)来表示指针。

int *ptr; // 声明一个指向整数的指针

指针在 C 和 C++ 中广泛用于三个主要
用途:
在堆上分配新对象
将函数传递给其他函数
循环访问数组或其他数据结构中的元素。

在堆上分配新对象

堆的概念在这里跟栈一起提一下。

堆的本质其实就是空闲内存,在C++中被称为自由储存区,只要你程序加载后,没有占用的空闲内存,都是自由存储区,用new和malloc申请一块新的内存区域,都是由操作系统从对上操作。

栈是程序在编译的时候就确定了大小的一段内存区域,主要用于临时变量的存储,栈的效率高于堆,但容量有限。
栈容量问题
如上面例子所示,数组空间限制了大小。已知我电脑内存为16GB,通过计算上面的数组限制大小看到:
在这里插入图片描述
我电脑限制的大小大致为4GB,具体环境具体分析。

动态内存分配-C

C语言的动态内存分配由好几种方式malloc,calloc,realloc。在这里主要举例分析下malloc。

malloc
void* malloc(size_t size);

malloc将为用户分配size_t字节个内存,并且返回内存分配的地址,如果分配失败则返回0.
在这里插入图片描述
由于malloc返回的是内存分配的地址,此时我们自然而然应该想到指针,因为指针是存储地址的变量。就像我们定义一个int类型的变量存储整数一样,我们定义一个指针来存储地址。
然而通过上述例子可知代码报错了,这里是因为malloc返回的是void*类型,也即void类型的指针,这个时候我们如果想其不报错可以更改指针类型或者强制转换。
更改指针类型
在这里插入图片描述
强制转换
在这里插入图片描述
以上两种方式都不会报错,根据实际情况使用。

小见解:很多人可能在理解指针的时候不太清楚,这个时候我们把指针当作变量来理解就行。正如int类型的变量存储的是整数,int*类型的变量存储的是指向整型的指针。

为什么说动态分配在堆?

在这里插入图片描述
a和b都是在栈上分配的内存,它们的位置相隔很近,而动态内存分配的地址则相差极大。

分配内存大小问题

malloc分配内存大小是按字节分配的,例如sizeof(int)就是分配了存储一个int类型数据的字节数,也即四字节。

释放内存

我们在动态分配内存的时候也要进行释放内存,防止内存泄露。释放内存我们使用的是free函数:

void free(void* _Block);

_Block是要释放的内存地址。
在这里插入图片描述
由上述例子可以看出来,我们在分配一个内存后,让那部分内存存储整数5,在未释放内存时,我们能正常访问,但将p指向的内存释放之后产生一串奇怪的数,但p指向的内存地址是没有改变的,只是那部分内存不可用了。
这个时候我们就产生了新的问题—悬挂指针。当程序释放了一块内存,但仍然保留对该内存的指针引用时,就会产生悬挂指针。针可能会导致程序运行时出现未定义的行为,因为程序试图访问已经释放的内存区域,这可能导致数据损坏、程序崩溃或安全漏洞。正确的做法是,在释放内存后,应该将指针 p 设置为 nullptr 或者将其赋值为另一个有效的内存地址。这样可以避免悬空指针引发的问题。

#include <iostream>
#include <cstdlib> // 包含 malloc 和 free 函数所需的头文件

int main() {
    int a = 10;
    int b = 100;
    std::cout << "a地址:" << &a << std::endl;
    std::cout << "b地址:" << &b << std::endl;

    int* p = (int*)malloc(sizeof(int));
    *p = 5;
    std::cout << "p地址:" << p << std::endl;
    std::cout << "*p:" << *p << std::endl;

    free(p); // 释放内存

    std::cout << "p地址:" << p << std::endl; // 输出已释放的内存地址
    // std::cout << "*p:" << *p << std::endl; // 这一行会导致未定义行为

    // 在释放内存后,将指针设置为 nullptr
    p = nullptr;
    
    // 或者分配新的内存地址
    // p = (int*)malloc(sizeof(int));
    // *p = 10;

    // std::cout << "*p:" << *p << std::endl; // 输出新分配的内存值
    // std::cout << "p地址:" << p << std::endl; // 输出新分配的内存地址

    return 0;
}

另外:在这里插入图片描述
我在释放之后重新给那部分内存赋值,但没有报错。这只是一种未定义的行为,这意味着结果是不确定的,可能会因为编译器、操作系统或者其他因素而有所不同。
在许多情况下,这种行为可能会导致程序崩溃或产生奇怪的结果,因为释放的内存已经被操作系统回收,并且可能已经被其他程序使用,或者被标记为不可访问的。然而,在某些情况下,你可能会幸运地看到一些意料之外的结果,这只是因为内存被释放后,该地址的内容仍然保留着之前的值,但这并不是一个稳定的行为,不应该依赖于它。

calloc

calloc 函数用于分配一块指定大小的内存块,并且将内存块中的每个字节都初始化为零。

void* calloc(size_t num_elements, size_t element_size);
num_elements: 指定要分配的元素数量。
element_size: 指定每个元素的大小(以字节为单位)。
realloc
void* realloc(void* _Block,size_t _Size);

realloc 函数用于重新分配之前分配的内存块的大小。它接受两个参数:一个指向先前由 malloccallocrealloc 分配的内存块的指针,以及新的内存块大小(以字节为单位)。

当调用 realloc 函数时,它会尝试重新分配已分配内存的大小,可能会扩大或缩小内存块的大小。如果分配成功,它会返回一个指向重新分配后内存块的指针,原始内存块的内容也会被复制到新的内存块中,然后原始内存块会被释放。

如果无法满足重新分配的大小需求,realloc 函数可能会返回一个指针与原始内存块相同,并且不会释放原始内存块。在这种情况下,原始内存块保持不变,仍然有效,但是应该检查 realloc 返回的指针,以确保它指向重新分配后的内存块。

在使用 realloc 后,和使用 malloccalloc 一样,也需要小心处理悬空指针的问题。如果 realloc 返回一个不同的指针,需要确保原始指针不再被使用,并将其设置为 nullptr 或者另一个有效的地址。

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值