帮助C中的数组参数

时间:2009-09-26 20:58:07

标签: c

在下面的代码中,当执行行doit(x,y)时,传递给指针的是什么? xy的地址或xy的值?

#include <stdio.h>

int doit(int x[], int y[]) {
   x = y;
   x[0] = 5;
   y[2] = 10;
}

int main(void) {
   int x[2];
   int y[2];

   x[0] = 1;
   x[1] = 2;
   y[0] = 3;
   y[1] = 4;

   doit(x, y);
   printf("%d %d %d %d", x[0], x[1], y[0], y[1]);
}

4 个答案:

答案 0 :(得分:5)

首先,作为一个方便的小问题,您可能会发现像这样初始化数组更容易:

int x[2] = { 1, 2 };
int y[2] = { 3, 4 };

请注意,这仅适用于初始化。我们不能这样做:

int x[2];
x = { 1, 2 };

这是一个数组初始化。我们不是“分配”数组,因为数组不是左值,但我们仍然可以初始化它。请注意,我们未填写的任何值都将初始化为零:

int lots[100] = { 1 };

现在lots是一个包含100个元素的数组,lots[0]是1,lots[1]lots[99]都是0.我们保证这些值,因为我们已初始化数组。如果我们刚刚做了:

int lots[100];

然后我们声明了但是没有初始化,我们的数组,所以它像任何其他局部变量一样保存垃圾。一个常见的习语是:

int lots[100] = { 0 };

要将我们的数组初始化为全零 - 我们声明的一个值为0,其他值自动归零。

其次,为了解决您的实际问题,让我们看一下doit()

int doit(int x[], int y[]) {

这是非常基本的 - 它声明了一个需要两个数组的函数。但是你不能将数组传递给C中的函数,或者从C中的函数返回它们,那么这究竟意味着什么呢?

int doit(int *x, int *y) {

在C中,所有“数组”(传递给函数时)都是指向该数组第一个元素的指针。这就是为什么数组是零索引的原因。要访问数组的第一个元素,我们取消引用指针。要访问第二个元素,我们将sizeof(array type)添加到指针,并取消引用:

x[0] == *x
x[1] == *(x + 1)
// and so on

这是一个常见的产品如下:

x[2] == *(x + 2) == *(2 + x) == 2[x]

虽然不常用,但仍然非常酷。但是不要在实际代码中使用它。那肯定是很酷。

无论如何,所以我们的数组的地址(以及指向我们数组的第一个元素的指针)被传递给我们的doit()函数。接下来会发生什么:

    x = y;

这是说“告诉我们的本地x指针指向y。它不会更改原始x数组,因为我们正在分配指针,而不是数组(我们不能这样做。)因此,对于同一个数组,我们最终基本上有两个名称xy。对{{1 }}或x将反映在传递给y的数组中。

y

这是非常基本的。我们将数组的第一个元素设置为5,将第三个元素设置为10.这里没有问题,假设为 x[0] = 5; y[2] = 10; 传入的数组至少有三个元素(提示不祥的音乐)。

y

实际上,这是} 中较大的问题之一。我们是如何声明doit()的?

doit()

所以int doit(int x[], int y[]) 会返回doit(),但我们没有int声明!虽然有些编译器可能会在不同程度上接受这种情况,但要么返回一个值,或者将return的返回类型更改为doit()(我怀疑是你想要的),这样做会更好。什么都没有。如果你的函数返回void,你可以在最后省略void语句,或者你可以明确地说return没有参数,因为你什么也没有返回。 (别担心,这个怪物差不多完了。)

正如您所知,这里最大的问题是我们无法保证我们传递函数的数组将包含三个元素。我们甚至不能保证他们会成为阵列。我们可以这样称呼它:

return;

这是合法的。 C语言不会检查以确保传递函数的数组足够大,也不会为您提供任何用于检查数组大小的内置工具。这不起作用:

int i = 10;
doit(&i, &i);

它不起作用,因为它在编译器级别被重写为:

size_t array_size(int a[]) {
    return sizeof(a) / sizeof(a[0]);
}

并且size_t array_size(int *a) { return sizeof(a) / sizeof(a[0]); } 不是数组的大小,而是数组的指针的大小(指针只是内存中的数字地址,通常是计算机字) 。因此,虽然在sizeof(int *)函数中修改y[2]是合法的,但您在doit()中传递的数组没有足够的元素,因此我们得到未定义的行为。编译器可以做任何事情 - 它可以覆盖main(),或覆盖其他一些局部变量,或覆盖你的代码数据,或做任何想做的事情(经典的例子是让恶魔飞出你的鼻子)是一个有效的C编译器。这是C的危险 - 它不会检查以确保你遵守规则。这样做的原因是它意味着是一种快速的语言,能够生成高效的代码,并且检查你是否遵守规则使你的程序变慢。因此,如果您想确保遵守规则,您必须自己检查规则:

x

然后叫它:

void doit(int y[], size_t len) {
    y[0] = 5;
    if(len > 2)
        y[2] = 10;
    else
        y[len] = 10;
}

现在我们可以安全地呼叫#define SIZE 2 int y[SIZE] = { 3, 4 }; doit(y, SIZE); ,即使在小型阵列上也是如此。这种将数组长度作为单独的参数传递给函数的模式在C中非常常见,并且可以在大多数处理数组和指针的C标准库函数中找到。

答案 1 :(得分:2)

阵列的第一个成员的地址。

我们可以使用调试器来检查它,例如gdb。

Starting program: /tmp/doit 

Breakpoint 2, main () at doit.c:11
11  int x[]={1,2,4,5};
(gdb) n
12  int y[]={11,12,14,15};
(gdb) print x
$1 = {1, 2, 4, 5}
(gdb) print y
**$2 = {134520820, -1076989448, 134513312, -1207230476}**
(gdb) print &x[0]   <--- PRINT ADDRESS OF FIRST ELEMENT OF X.***
***$3 = (int *) 0xbfce71f4
(gdb) print &y[0]   <--- PRINT ADDRESS OF FIRST ELEMENT OF Y.***
$4 = (int *) 0xbfce71e4
(gdb) n
14  doit(x,y);
(gdb) step
//FUNCTION CALL IS DISPLAYED HERE. 
Breakpoint 1, ***doit (x=0xbfce71f4, y=0xbfce71e4)*** at doit.c:7
7 }
(gdb) 

传递给doit的x和y的值如下所示:

Breakpoint 1, ***doit (x=0xbfce71f4, y=0xbfce71e4)*** at doit.c:7

x是0xbfce71f4。那是数组X的第一个元素的地址。

y是0xbfce71e4。那是数组Y的第一个元素的地址。

另外,想想这个。

数组不能在C中赋值。我的意思是,x = y会产生编译错误。 (类似于:错误:赋值中的不兼容类型)。因此,如果每个参数都接收到一个数组,而不是第一个元素的地址,则代码将无法编译。

答案 2 :(得分:2)

当数组作为参数传递时,它们总是“降级”为简单指针。所以原型:

int doit(int x[], int y[]) ;

相当于

int doit( int* x, int* y ) ;

我更喜欢第二种,因为它很清楚真正发生的事情,第一种承诺是无法实现的。它可能表示预期的是指向数组的指针而不是指向单个对象的指针,但它对实际代码生成没有影响。通常这些函数有额外的参数来指定传递的数组的大小。

数组和指针之间的区别仅在于数组包含大小信息。例如,如下所示:

void fn( int x1[], int* x2 )
{
    printf( "sizeof(x1) = %u\n", sizeof( x1 ) ) ;
    printf( "sizeof(x2) = %u\n", sizeof( x2 ) ) ;
}

int main()
{
    int x[10] ;
    int* y = x ;
    printf( "sizeof(x) = %u\n", sizeof( x ) ) ;
    fn( x, y ) ;
}

将输出(在32位平台上):

sizeof(x) = 40
sizeof(x1) = 4
sizeof(x2) = 4

但x,x1和x2都是指同一个数组。

答案 3 :(得分:-4)

数组的地址传递给doit()函数,但“传递给指针”是什么意思?