2

我有这样的功能:

void myfunc(int** arr, int n) {
  int i, j;
  for(i=0; i<n; ++i) {
    for(j=0; j<n; ++j) {
      printf("%d,", *(arr + i*n + j) );  // Print numbers with commas
    }
    printf("\n");                        // Just breakline
  }
}

在其他函数中,我有一个像这样的二维数组:

int main() {
  int seqs[8][8] = {
    {0, 32, 36, 52, 48, 16, 20, 4},
    {0, 16, 20, 52, 48, 32, 36, 4},
    {0, 32, 36, 44, 40, 8, 12, 4},
    {0, 8, 12, 44, 40, 32, 36, 4},
    {0, 32, 36, 38, 34, 2, 6, 4},
    {0, 2, 6, 38, 34, 32, 36, 4},
    {0, 32, 36, 37, 33, 1, 5, 4},
    {0, 1, 5, 37, 33, 32, 36, 4}
  };

  // Call to myfunc
  myfunc(seqs, 8);  // This is the idea
}

但是编译器向我抛出了这个错误:

lab.c: In function 'main':
lab.c:75:5: warning: passing argument 1 of 'myfunc' from incompatible pointer type [enabled by default]
lab.c:4:6: note: expected 'int **' but argument is of type 'int (*)[8]'

将这个数组(seqs)传递给函数(myfunc)的正确方法是什么?

4

5 回答 5

5

在 C99 或 C11 中,你会这样做:

void myfunc(int n, int arr[n][n])
{
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < n; ++j)
            printf("%d,", arr[i][j]);
        printf("\n");
    }
}

请注意,大小在数组之前而不是之后。此功能将在以下情况下正常工作:

int main(void)
{
    int seqs[8][8] =
    {
        { 0, 32, 36, 52, 48, 16, 20, 4 },
        { 0, 16, 20, 52, 48, 32, 36, 4 },
        { 0, 32, 36, 44, 40,  8, 12, 4 },
        { 0,  8, 12, 44, 40, 32, 36, 4 },
        { 0, 32, 36, 38, 34,  2,  6, 4 },
        { 0,  2,  6, 38, 34, 32, 36, 4 },
        { 0, 32, 36, 37, 33,  1,  5, 4 },
        { 0,  1,  5, 37, 33, 32, 36, 4 },
    };
    myfunc(8, seqs);

    int matrix3x3[3][3] = { { 1, 2, 3 }, { 2, 4, 6 }, { 3, 6, 9 } };
    myfunc(3, matrix3x3);
}

有人问我:

你的例子确实看起来好多了,但它定义明确吗?真的n保证被评估过int arr[n][n]吗?函数参数的评估顺序不是未指定的行为吗?

旧标准 (ISO/IEC 9899:1999) 在 §6.7.5.2* Array declarators * 中说:

¶5如果 size 是一个不是整数常量表达式的表达式:如果它出现在函数原型范围的声明中,则将其视为被替换为*; 否则,每次对其进行评估时,它的值都应大于零。可变长度数组类型的每个实例的大小在其生命周期内不会改变。如果大小表达式是运算sizeof符操作数的一部分,并且更改大小表达式的值不会影响运算符的结果,则未指定是否计算大小表达式。

它给出了一个例子(它是非规范性文本,因为它是一个例子,但强烈地表明了预期):

示例 4 所有可变修改 (VM) 类型的声明必须在块范围或函数原型范围内。static使用or存储类说明符声明的数组对象extern不能具有可变长度数组 (VLA) 类型。但是,使用static存储类说明符声明的对象可以具有 VM 类型(即,指向 VLA 类型的指针)。最后,使用 VM 类型声明的所有标识符都必须是普通标识符,因此不能是结构或联合的成员。

extern int n;
int A[n];                       // invalid: file scope VLA
extern int (*p2)[n];            // invalid: file scope VM
int B[100];                     // valid: file scope but not VM
void fvla(int m, int C[m][m]);  // valid: VLA with prototype scope
void fvla(int m, int C[m][m])   // valid: adjusted to auto pointer to VLA
{
    typedef int VLA[m][m];      // valid: block scope typedef VLA
    struct tag {
        int (*y)[n];            // invalid: y not ordinary identifier
        int z[n];               // invalid: z not ordinary identifier
    };
    int D[m];                   // valid: auto VLA
    static int E[m];            // invalid: static block scope VLA
    extern int F[m];            // invalid: F has linkage and is VLA
    int (*s)[m];                // valid: auto pointer to VLA
    extern int (*r)[m];         // invalid: r has linkage and points to VLA
    static int (*q)[m] = &B;    // valid: q is a static block pointer to VLA
}

还有其他示例显示了可变修改的函数参数。

此外,在 §6.9.10 Function definitions中,它说:

¶10 在进入函数时,每个可变修改参数的大小表达式都会被评估,并且每个参数表达式的值都被转换为相应参数的类型,就像通过赋值一样。(作为参数的数组表达式和函数指示符在调用之前被转换为指针。)

于 2014-02-17T07:22:02.687 回答
2

数组和指针不一样。同样,二维数组和指向指针的指针也不是一回事。seqsmismatches 函数参数的类型,您可以将签名修改myfunc为:

void myfunc(int arr[][8], int n) 

或者,您可以修改seqs为指向指针的真实指针。

于 2014-02-17T06:44:57.567 回答
1

您将 声明int**为具有静态大小的数组,因此它的签名是int *[8]. 您可以通过在调用时转换数组来消除编译器错误myfunc

myfunc((int **)seqs, 8);

在实际程序中,这样的数组很可能是动态生成的,您不必这样做。

于 2014-02-17T06:45:16.290 回答
0

当您将二维数组定义为

int seqs[8][8];

的类型seqs是 8 个数组,elements其中每个element的类型都是array of 8 integers。当您传递seqs到 时myfuncseqs它被隐式转换为指向其第一个元素的指针(当您将数组传递给函数时会发生这种情况),即类型* elementelement是类型int [8]。因此seqs隐式转换为类型int *[8]- 指向 8 个整数数组的指针。

在您的函数myfunc中,参数的类型arr显然int **与您在调用它时传递的类型不同main。它们是不同的类型并且具有不同的指针算法。结果编译器抱怨。

您应该将类​​型更改arrint *[8]

void myfunc(int arr[][8], int n);

arr这里不是一个数组,而是一个指向 8 个整数数组的指针,您不妨声明myfunc

void myfunc(int *arr[8], int n);

它们完全相同。

编辑

关于您在此处的评论,您不能执行以下操作并期望事情正常工作。

// in main
myfunc((int **)seqs, 8);

// the above call is essentially doing this 
int **arr = (int **)seqs;

seqs并且arr是不兼容的类型,并且类型转换seqs抑制了编译器发出的警告,这更糟糕,因为类型转换不能纠正事情,并且看起来好像一切正​​常。

让我们看看为什么。(在函数调用之后。)

seqs (type: array of 8 int[8] type; value: address of the location seqs[0][0])
*seqs (type: int[8] type, value: address of the location seqs[0][0])

arr (type: pointer to a pointer to an int; value: address of the location seqs[0][0])
*arr (type: pointer to an int; value: seqs[0][0])
**arr (type: int; value: value at the address seqs[0][0])

上面的断言可以用assert宏来检查。所以我们看到,当我们做**arr我们实际做的事情是将值seqs[0][0]视为内存地址并尝试访问该位置的值。这显然是错误的!它可能导致未定义的行为或最有可能的分段错误。

没有办法通过类型转换使seqs(它在初始化时计算的值) 的值arr像 a 一样int **。这也表明我们应该小心类型转换值,除非我们知道并确定我们在做什么,否则不应该这样做。

因此,您必须更改myfunc函数签名以执行您想要的操作。

于 2014-02-17T07:19:55.593 回答
0

您从函数中的数组访问值的方式将为您提供正确的元素,其中包含简单的int *而不是int **. 与*(arr + i*n + j). 将i*n您带到正确的行,然后将您带到j列。因此,将您的函数标题行更改为:

void myfunc(int* arr, int n) {

...(带有一个'*'),并传递第一个元素的地址而不是裸数组名称,如下所示:

myfunc(&seqs[0][0], 8);

..或第一行:

myfunc(seqs[0], 8);
于 2014-02-17T07:09:09.727 回答