函数值传递底层原理:数组与普通变量传参差异问询
这是个非常戳中C/C++传参本质的好问题——很多刚接触这门语言的开发者都会被数组传参的“特殊表现”搞晕。咱们一步步拆解你的疑问:
一、普通变量的「值拷贝」到底是怎么运作的?(对应你的Ex2)
先看你第二个示例里的普通变量传参:
void interChange(int a, int b) { int tmp; tmp = a; a = b; b = tmp; } int main() { int a = 5, b = 3; interChange(a, b); return 0; }
当你在main里调用interChange(a, b)时,编译器会做这几件事:
- 为
interChange函数创建一个独立的栈帧(栈是程序运行时用来存储局部变量、函数调用信息的内存区域)。 - 在这个新栈帧里,生成两个全新的局部变量
a和b——注意,它们和main里的a、b只是名字相同,完全是两个不同的内存块。 - 把
main中a的值(5)拷贝到interChange栈帧的a里,把main中b的值(3)拷贝到interChange栈帧的b里。
函数内部交换的只是这两个局部变量的值,和main里的变量没有任何关系,所以函数执行完后,main里的a、b完全不会变。
你问的几个小问题:
为何普通变量是值拷贝而非传递地址?
这是C/C++的默认设计:默认传参方式就是值传递,只有当你显式声明参数为指针(int* a)或引用(int& a)时,才会传递地址/引用。编译器处理值传递时,就是直接复制变量的内容到函数栈帧,不需要额外处理地址逻辑(除非你手动用&取地址)。为什么
main和interChange里的&a地址非常接近?
这和栈的生长方向有关:大多数系统的栈是从高地址向低地址生长的。main函数先运行,它的局部变量会在栈上占据一块高地址区域;当调用interChange时,栈会向低地址方向扩展,为函数的局部变量分配空间。所以两个栈帧里的变量地址自然会比较接近——比如你看到的0x69fed8(main的a)和0x69fe80(interChange的a),后者地址更低,完全符合栈的生长规律。
二、数组传参的「特殊值传递」:为什么看起来像传指针?(对应你的Ex1)
再看第一个示例的数组传参:
void function(int arr[]) { cout << arr << endl; // 输出首个元素的地址 cout << sizeof(arr); // 32位系统下为4字节(指针大小) } int main() { int vector[] = {1,2,3,4,5,6,7}; function(vector); return 0; }
这里的核心是:数组作为函数参数时,会自动「退化」为指向数组首元素的指针——不管你写的是int arr[]还是int* arr,编译器都会把它解析成int* arr。
那为什么说这还是「值传递」?因为传递的是指针的值——也就是数组首元素的地址。当你调用function(vector)时,编译器会把vector(数组名本身就是首元素的地址)的值拷贝到函数的arr参数里(这个arr是个指针变量,在32位系统里占4字节)。
所以你看到的现象就很好解释了:
- 打印
arr时,输出的是数组首元素的地址,和vector的地址一致,因为拷贝的就是这个地址值。 sizeof(arr)得到的是指针的大小,而不是数组的大小——因为arr已经是指针,不再是数组了,自然丢失了数组的长度信息。- 如果你在函数里修改
arr[0],原数组的第一个元素会被改变——这是因为你通过指针直接操作了原数组的内存空间,但本质上还是值传递(传递的是地址的拷贝),只是这个地址指向了原数组的内存。
为什么数组不直接做全量值拷贝?
这是语言设计的权衡:如果数组很大(比如1000个int元素),拷贝整个数组会非常消耗内存和时间,效率极低。所以C/C选择了数组退化指针的方式,兼顾效率和语法简洁。如果真的要传递数组的副本,你可以把数组包装成结构体,或者在C里用std::array来实现。
三、核心区别总结
- 普通变量值传递:传递的是变量内容的拷贝,函数内的修改只作用于函数栈帧里的新变量,完全不影响原变量。
- 数组「值传递」:实际传递的是数组首元素的指针值,函数内可通过指针修改原数组内容,但本质仍是值传递(传递的是地址的拷贝),且数组参数会退化为指针,丢失长度信息。
内容的提问来源于stack exchange,提问作者Cătălina Sîrbu

