指针是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
函数用于重新分配之前分配的内存块的大小。它接受两个参数:一个指向先前由 malloc
、calloc
或 realloc
分配的内存块的指针,以及新的内存块大小(以字节为单位)。
当调用 realloc
函数时,它会尝试重新分配已分配内存的大小,可能会扩大或缩小内存块的大小。如果分配成功,它会返回一个指向重新分配后内存块的指针,原始内存块的内容也会被复制到新的内存块中,然后原始内存块会被释放。
如果无法满足重新分配的大小需求,realloc
函数可能会返回一个指针与原始内存块相同,并且不会释放原始内存块。在这种情况下,原始内存块保持不变,仍然有效,但是应该检查 realloc
返回的指针,以确保它指向重新分配后的内存块。
在使用 realloc
后,和使用 malloc
或 calloc
一样,也需要小心处理悬空指针的问题。如果 realloc
返回一个不同的指针,需要确保原始指针不再被使用,并将其设置为 nullptr
或者另一个有效的地址。