数组是按值传递还是按引用传递?

13

我确定知道

function(int *a); function(int a[]);

在 C 语言中,数组名作为函数参数时,在翻译成指针类型时与 int *a 相同。

int *a = malloc(20);
int b[] = {1,2,3,4,5};

这两者并不相同,第一个是指针,第二个是数组。当我调用函数(function(int *a))时会发生什么?我知道b在栈上,那它是如何传递给那个函数的呢?

其次,字符串:

char *c1 = "string";
char c2 [] = "string";
在这种情况下,我不知道c1在哪里,而我假设c2在堆栈上。 假设函数现在是:function(char *c),与function(char c[])相同,当我调用function(c1)和function(c2)时,字符串会按引用传递还是按值传递?

在这种情况下,我不知道c1的位置,我认为c2位于堆栈上。 假设现在函数是:function(char *c),与function(char c[])相同,当我调用function(c1)和function(c2)时,字符串将被按引用传递还是按值传递?


是的,我已经做了你提到的所有事情,但现在我正在尝试通过汇编眼镜看C语言。 - AR89
然后,使用 gcc -Wall -fverbose-asm -S yourfile.c 进行编译,然后查看 yourfile.s - Basile Starynkevitch
6个回答

21

这里有一个关键点需要指出,所有东西都是通过值来传递的,例如,这将会把a的一个拷贝传递给foo()(它恰好是某个内存地址的指针):

int *a = malloc(20);
foo(a);

这就是为什么如果你在foo()中做了类似这样的事情,它并不会真正改变main()中指针a的值,而是改变了本地副本

foo(int *a)
{
  a = NULL; /*changes local copy of the pointer*/
}
换句话说,你可以使用foo()中的本地a副本来更改由'a'指向的内存,但不能更改在main()a所指向的内容。

现在,要传递“按引用传递”的东西,需要向函数传递一个指向指针的指针的副本(类似于a->b->memory):

int *a = malloc(20);
foo(&a);

因此,当你在foo()中为它赋值时,会改变main()中的指针:

foo(int **a)
{
  *a = NULL; /*changes the pointer in main */
}

现在回答你其他问题中的一些,当你使用数组名时,它会被转换为指向数组第一个元素的指针:

int *a = malloc(20);
int b[] = {1,2,3,4,5};
foo(a);
foo(b);

最后两个函数调用是等价的,它们都传递了指向一些内存的第一个元素的指针,不同之处在于 a 的内存是在堆上分配的,而 b 的内存则是在栈上分配的。

最后是字符串,以下两种情况类似,原理相同,但第一个是一个常量字符串字面量,应该定义为const,并且不应尝试在任何地方修改它,但可以更改第二个字符串:

const char *c1 = "string";
char c2 [] = "string";

 

->

 


6
虽然这是正确的,但对于初学者来说不太具有教育意义。最好说明 func (int x) 是按值传递,而 func (int* x) 是按引用传递。因为按值还是按引用实际上取决于函数的 _意图_。否则,你只能说你的例子 foo(int **a) 并没有通过引用传递任何东西,它只是通过值传递了一个指向指针的指针。 - Lundin
3
@Lundin 我认为,在我个人看来,说某个东西是按引用传递的,是所有初学者尝试在函数内部分配指针并期望它在其他地方更改的原因。 - iabdalkader
2
一个没有编程经验的初学者可能不太可能做到这一点,相比之下,一个学习C语言的Java程序员可能更容易。无论如何,所有初学者都会尝试从函数中返回指向本地变量的指针。我认为像“按引用传递”或者“指针”这样模糊的术语对于初学者来说并不是很有帮助,更好的方法是解释在二进制代码下发生了什么。 - Lundin

7

基于K&R2的内容

When an array name is passed to a function, what is passed is the 
location of the initial element. Within the called function, this 
argument is a local variable, and so an array name parameter is a 
pointer, that is, a variable containing an address.

参数声明中的char c2 []只是char* c2的语法糖。
在C语言中,所有内容都以值的形式传递。关于这一点的进一步解释,请使用此链接
此外,Eli Bendersky撰写了一篇优秀的文章,讨论了同样的问题。

我已经知道了语法糖,顺便感谢你的文章。 - AR89

0
      int *a = malloc(20);
                         ===========
    |a|----------------> | | | | | |  
                         ===========
    a is a pointer          20 bytes on heap each of 4 bytes 
    to int on stack 


    int b[] = {1,2,3,4,5};


     =================================
     |   1 |  2   |  3  |  4  |  5   |
     =================================
     b[0]   b[1]    b[2]  b[3]   b[4]   
Full array is on stack


char *c1 = "string";
char c2 [] = "string";


In case of "string" it's string literal and allocated on readonly memory.

In first case , |c1| ----> |s|t|r|i|n|g|\0|

In second case ,  ============================================
                  | s  |  t   |   r  |  i  |  n  |  g  | \0  |
                  ============================================
                  c2[0] c2[1]   c2[2] c2[3] c2[4] c2[5]  c2[6]

The full char array is on stack.

如果你使用char c2[] = "string",它不会被存储在只读内存中。 - Dietrich Epp
字符串将在readonly上,它的副本将被复制到堆栈上的c2数组中。 - Omkant
这是一种可能的实现方式,但你正在谈论实现细节。最终结果是不在只读内存中的东西。 - Dietrich Epp
@DietrichEpp 从技术上讲,“字符串”字面值始终会被放置在只读内存中,但编译器可以自由地优化其在那里的分配,并仅为c2分配空间。也就是说,如果编译器是为基于纯RAM的系统(如PC)编写的。否则,无法做任何事情,字符串字面值必须在ROM中分配。 - Lundin
@Lundin:从技术上讲,根本没有只读存储器存在的要求(我可以想到几个没有它的C实现),我们唯一确定的是c2不在只读存储器中。 - Dietrich Epp

-1
如果你认为这是“按值调用”,那就试着运行这个代码:
void bubbleSort(int a[],int n){            //or int *a
    printf("\nBUBBLE SORT");
    int i,j;
    for(i=n-1;i>0;i--)
        for(j=0;j<i;j++)
            if(a[j]>a[j+1])
                swap(&a[j],&a[j+1]);
    displayArray(a,n);
}
main(){
    int n=5,a[n],i,j;
    initArray(a,n);
    displayArray(a,n);
    bubbleSort(&a,n);
    displayArray(a,n);
}

在bubbleSort()和displayArray()中,在调用bubblesort()之后,将显示相同的结果。
如果在a是数组时调用function(a),它实际上会传递a的基地址。因此,它基本上是按引用调用。
另外,
char *c="blah-blah"; and,
char c[]="blah-blah";

完全相同! 在这两种情况下,c都指向字符串中的第一个元素。此外,第一个字符串不是常量。尝试更改它。它会改变。


-2

编辑:下面的答案是错误的。
"数组永远是通过引用传递的。" 这是错误的。请参见iabdalkader的答案了解为什么。 但是,我将保留下面的答案,因为它使用结构体封装技巧将数组的副本传递给函数。


数组总是通过引用(指针)传递。

如果您想使用值传递它们,请将它们封装在struct中。这将导致对原始数组的副本进行操作。下面是一个示例:

foo的第一个参数是结构对象,它保存了数组的副本。第二个参数是数组引用。

#include <stdio.h>

struct a{
    int i[2]; // An array encapsulated in struct.
};

void foo(struct a b, int j[]){
    b.i[0]=30;
    j[1]=30;
}

int main(){
    struct a c;
    c.i[0]=10;
    c.i[1]=20;
    printf("Before %d %d\n",c.i[0], c.i[1]);
    foo(c, c.i);
    printf("After %d %d \n",c.i[0], c.i[1]);
    return 0;
}


$ ./a.out 
Before 10 20
After 10 30 

-3
数组是通过引用传递的,为什么?因为你传递了指针地址按值传递,所以在该指针上进行更改将会改变指向的内存而不是指针地址本身,因此更改指针将不会替换函数外的原始指针,所以当你在函数内部键入a = ANOTHER_POINTER时,它不会丢失使用函数后传递的数组。 int a[]等同于int a,当调用函数foo(int a)时,它将获取指针地址。
现在,如果你想要更改指针本身,你可以通过引用传递指针地址foo(int *& a),这样更改指针地址a = ANOTHER_POINTER将会改变指针的地址。

感谢提到foo(int *& a)作为改变原始指针的一种方式。 - AR89
2
不,你不能通过C++中的引用机制在C语言中传递指针地址。foo(int *& a) 会无法编译。 - Lundin

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