函数调用场景下的C/C++指针异常行为排查
我有12年软件开发经验,平时主要用Java/C#和Python,最近捡起来学C++,从指针基础开始练手写动态数组和内存管理(纯学习用,生产环境肯定用vector)。写了下面这段代码:
#include <iostream> #include <algorithm> // 需包含头文件才能使用std::copy using namespace std; int * CreateDynamicArray(int capacity, int& size){ size = 0; cout << "Creating Dynamic Array of size: " << capacity << endl; return new int[capacity]; } void InsertElement(int* dynamicArray, int element, int& size, int& capacity){ dynamicArray[size] = element; size++; } void ResizeDynamicArray(int* dynamicArray, int& size, int newCapacity){ int * temp = new int[newCapacity]; copy(dynamicArray, dynamicArray + size, temp); delete[] dynamicArray; dynamicArray = temp; } int main() { int capacity = 1; int size; int * dynamicArray = CreateDynamicArray(capacity, size); InsertElement(dynamicArray, 20, size, capacity); ResizeDynamicArray(dynamicArray, size, capacity + 1); // 此处访问dynamicArray[0]会得到随机值 cout << dynamicArray[0] << endl; delete[] dynamicArray; return 0; }
把逻辑全写在main里时一切正常,但拆成函数后出问题了——在ResizeDynamicArray里,新建temp指针拷贝数据,delete旧数组后把dynamicArray指向temp,但函数返回后,main里的dynamicArray[0]从20变成了随机值;要是删掉delete[] dynamicArray这行就没毛病。
疑问解答
1. 为何删除旧指针会影响新指向的地址?
不是删除旧指针影响了新地址,而是你在ResizeDynamicArray里修改的dynamicArray只是原指针的副本。main里的原指针根本没被更新,还指着原来被delete掉的内存地址。当旧数组内存被操作系统回收后,再访问main里的dynamicArray就是访问野指针,值自然是随机的。
要是不删旧数组,那块内存只是暂时没被系统覆盖,能看到旧值纯属巧合,本质是未定义行为,随时可能崩。
2. 为何仅在函数中出现该行为?
写在main里时,你修改的是同一个指针变量,直接更新它的指向当然生效。但拆成函数后,C++默认是值传递——函数会创建一个和原指针值相同的副本,你在函数里修改副本的指向,完全不会影响main里的原指针。这和你把int变量传给函数,函数改副本不影响原变量是一个道理,只不过这里传的是指针的地址值。
3. 如何避免?双指针的原理是什么?
核心是让函数能直接修改main里的原指针,有两种常用方法:
方法一:使用指针的引用(int*&)
把ResizeDynamicArray的参数改成指针的引用,这样函数里操作的就是main里的原指针,而非副本:
void ResizeDynamicArray(int*& dynamicArray, int& size, int newCapacity){ int * temp = new int[newCapacity]; copy(dynamicArray, dynamicArray + size, temp); delete[] dynamicArray; dynamicArray = temp; // 直接修改main里的原指针 }
原理和你传int&给函数能修改原int变量一样,指针的引用就是让函数直接操作原指针变量。
方法二:使用双指针(int**)
双指针是指向指针的指针,把main里指针的地址传给函数,函数通过这个地址修改原指针的值:
void ResizeDynamicArray(int** dynamicArray, int& size, int newCapacity){ int * temp = new int[newCapacity]; copy(*dynamicArray, *dynamicArray + size, temp); delete[] *dynamicArray; *dynamicArray = temp; // 解引用后修改main里原指针的指向 } // main里调用时传指针的地址: ResizeDynamicArray(&dynamicArray, size, capacity + 1);
原理是:main里的dynamicArray是存数组地址的指针变量,&dynamicArray是这个指针变量本身的地址。函数里用int**接收后,*dynamicArray就是对该地址解引用,拿到main里的原指针变量,修改它的指向就会直接生效。
两种方法都可行,指针引用更直观,双指针是C语言常用写法(因为C没有引用特性)。
内容的提问来源于stack exchange,提问作者Stealthguy

