在C语言中如何通过引用传递数组

24

我是C语言的新手,有一个疑问。

由于C函数会创建其参数的本地副本,所以我想知道为什么以下代码能按预期工作:

void function(int array[]){

    array[0] = 4;
    array[1] = 5;
    array[2] = 6;   
}

int main(){

    int array[] = {1,2,3};

    function(array);

    printf("%d %d %d",array[0],array[1],array[2]);

    return 0;
}

使用该行输出为4 5 6。

为什么这个有效而以下的不起作用?

void function(int integer){

    integer = 2;
}

int main(){

    int integer = 1;

    function(integer);

    printf("%d",integer);

    return 0;
}

在这种情况下,输出结果只是1。

简短版:如果将它们作为数组传递,为什么函数可以修改其父变量的值?

谢谢大家!


2
任何认为数组“实际上”是指针的人都需要阅读comp.lang.c FAQ第6节。简而言之:不,它们不是。 - Keith Thompson
5个回答

31

这是由于数组往往会衰变为指针的事实引起的。

int a[] = { 1, 2, 3 };
int* p = a; // valid: p is now the address of a[0]
a = p;  // NOT valid.

printf("a = %p\n", a);
printf("p = %p\n", p); // prints same address as a

ap将打印相同的值。

与其他人所说的相反,a不是指针,而只是可以衰变成一个指针。http://c-faq.com/aryptr/aryptrequiv.html

在您的第一个function()中,传递的是数组第一个元素的地址,并且函数体对其进行了解引用。实际上,编译器将函数原型视为:

void function(int* array /*you wrote int array[]*/){
    array[0] = 4;
    array[1] = 5;
    array[2] = 6;   
}

function(&array[0]);

这是必须发生的,因为你说了“大小未知的数组”(int array[])。编译器无法保证通过值传递所需的堆栈量,因此将其衰减为指针。

---- 编辑 ----

让我们结合你们两个的例子,并使用更明显的名称使事情更加清晰。

#include <stdio.h>

void func1(int dynArray[]) {
    printf("func1: dynArray = %p, &dynArray[0] = %p, dynArray[0] = %d\n",
             dynArray, &dynArray[0], dynArray[0]);
}

void func2(int* intPtr) {
    printf("func2: intPtr = %p, &intPtr[0] = %p, intPtr[0] = %d\n",
             intPtr, &intPtr[0], intPtr[0]);
}

void func3(int intVal) {
    printf("func3: intVal = %d, &intValue = %p\n",
             intVal, &intVal);
}

int main() {
    int mainArray[3] = { 1, 2, 3 };
    int mainInt = 10;

    printf("mainArray = %p, &mainArray[0] = %p, mainArray[0] = %d\n",
             mainArray, &mainArray, mainArray[0]);
    func1(mainArray);
    func2(mainArray);

    printf("mainInt = %d, &mainInt = %p\n",
             mainInt, &mainInt);
    func3(mainInt);

    return 0;
}

在ideone上有实时演示:http://ideone.com/P8C1f4

mainArray = 0xbf806ad4, &mainArray[0] = 0xbf806ad4, mainArray[0] = 1
func1: dynArray = 0xbf806ad4, &dynArray[0] = 0xbf806ad4, dynArray[0] = 1
func2: intPtr = 0xbf806ad4, &intPtr[0] = 0xbf806ad4, intPtr[0] = 1

mainInt = 10, &mainInt = 0xbf806acc
func3: intVal = 10, &intValue = 0xbf806ad0
func1func2中,"dynArray"和"intPtr"是局部变量,但它们是指针变量,从主函数中接收"mainArray"的地址。
这种行为仅适用于数组。如果您将数组放在结构体中,则可以按值传递它。

1
传递的不是数组的地址,而是数组第一个元素的地址。(相同的内存地址,不同的类型。) - Keith Thompson
所以,举个例子,当我们使用array[1]时,它是否已经隐式地对指针进行了解引用?此外,我尝试将数组初始化为int array[3],并在函数头中也使用了in array[3],但它仍然会打印出4 5 6,即使编译器可以保证通过值传递一切所需的堆栈量。我有没有漏掉什么?谢谢! - Costagero
@Rogério,给定“int a[]={1,2,3}”,表达式a[1]与*(a+1)完全等价。也就是说,“a”是第一个int的地址,“a+1”是紧随其后的int的地址,“*(a+1)”是该表达式指向的int。下标运算符可以被视为语法糖。至于你的第二个观点,请参见我的早期评论。在C语言中,您无法按值传递数组。即使编译器有足够的信息来执行此操作,这也不是语言定义的方式。 - John Auld
我稍微改了一下答案:在动态大小数组的情况下,衰减必须发生,因此出现了这种情况,但在其他情况下也会发生。同时,在 ---- 编辑 ---- 中添加了一些额外的解释。 - kfsone
这是否意味着静态大小的数组将按值传递?或者仅在包装在结构体中时? - Max
@Max 传递静态大小的数组,void fn(char in[8]); char c[8]; fn(c);,会导致衰减为指针。typedef 别名也是如此。只有当作为结构体的一部分传递时才按值传递。请参见 https://godbolt.org/g/nkIBAm - kfsone

3
一个传递给函数的数组会被转换为指针。当您将指针作为参数传递给函数时,您只是将变量在内存中的地址传递给函数。因此,当您修改数组单元格的值时,您编辑了传递给函数的地址下的值。
当您将一个简单的整数传递给函数时,该整数会被复制到堆栈中。当您在函数内部修改整数时,您修改的是整数的副本,而不是原始整数。
C语言中有三种类型的内存:堆栈、堆(动态分配的内存)和程序指令所存放的代码部分。
在这个通过函数传递的数组的情况下,它是一个指针(另一个变量的地址),它存储在堆栈中。当我们调用函数时,我们将指针复制到堆栈中。
在整数的情况下,它也存储在堆栈中。当我们调用函数时,我们复制整数。
如果我们想要修改整数,可以传递整数的地址来修改指针下的值,像这样:
void function(int *integer)
{
    *integer = 2;
}

int main()
{
    int integer = 1;
    function(&integer);

    printf("%d", integer);

    return 0;
}

3
不,数组不是指针。在大多数情况下,数组表达式会被隐式转换成指向数组对象第一个元素的指针。 - Keith Thompson
“转换为指针”这种说法是错误的,并且可能会导致来自C#等语言的程序员感到困惑。在C/C++中,数组代表两个东西。它既是一个包含给定类型的N个实例的内存块,又是该内存的指针。ABI没有提供任何机制来转发此类数据,因此您只能通过完全匹配要传递的参数来实现 - 要么是显式的硬编码指纹,要么是C++中的模板来为您创建一个。int f(char (&arg)[64])template<class T, size_t N> int f(T (&arg)[N]) - kfsone

1

“按引用传递”和“按值传递”之间存在区别。

按引用传递会导致指向内存位置的引用,而按值传递直接传递数值。数组变量始终是引用,因此指向内存中的位置。整数默认情况下将按值传递。


1
在第一段代码中,您传递的是指向数组顶部元素的数组地址。因此,当您在函数中修改值并返回到主函数时,仍然访问相同地址中的同一数组。这被称为按引用传递。
然而,在第二种情况下,整数的值从主函数复制到调用函数。换句话说,两个整数在内存中的不同地址。因此,修改一个不会修改另一个。

0

数组名是指向数组中第一个元素的指针。在第一个代码示例中,您已经传递了一个指向包含第一个数组元素的内存位置的指针。在第二个代码示例中,您已经通过值传递了一个整数,因此它与名为“integer”的局部变量无关。

请检查该链接

按引用传递和按值传递

C++中的按引用/值传递


在大多数情况下,数组名_is converted to a pointer_指向数组的第一个元素。数组名不是指针。 - This isn't my real name

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接