【问题标题】:C - What is happening when specifying an array parameter with another parameter as its sizeC - 使用另一个参数作为其大小指定数组参数时会发生什么
【发布时间】:2021-10-30 07:59:54
【问题描述】:

我遇到了这段代码,但我无法弄清楚它是如何/为什么起作用的:

void cistore(int bucketsize, int data[][bucketsize])
{
}

int main()
{
    return 0;
}

这里到底发生了什么?我希望 C 编译器(在本例中为 gcc)仅在 bucketsize 在编译时可确定的情况下才允许这样做。但即使在编译时无法知道 bucketsize,gcc 也不会抱怨。 gcc 是如何处理的?

【问题讨论】:

  • 这不会在任何版本的 gcc AFAIK 上编译
  • 我已将其更改为实际上无需任何#includes 即可编译的版本
  • 它是 C 中的 VLA(可变长度数组)。This answer 展示了将 VLA 传递给 C 中的函数的各种方法。
  • 我明白了,谢谢。您可以将此作为答案重新发布,以便我接受吗?

标签: arrays c gcc matrix multidimensional-array


【解决方案1】:

这里到底发生了什么?

自 C99 以来,C 支持变长数组,其长度在运行时确定。在 C99 中,它是一项强制性功能,但自 C11 起,它已成为一项可选功能。许多现代 C 编译器都支持它,微软的明显例外。

void cistore(int bucketsize, int data[][bucketsize])

, data 被声明为指向bucketsize 类型为int 的元素的数组的指针。指向的对象是一个变长数组,至少从函数的角度来看是这样。

我希望 C 编译器(在这种情况下 gcc) 仅在 bucketsize 在编译时可确定时才允许这样做。

惊喜!

但即使在编译时无法知道 bucketsize, gcc 没有抱怨。 gcc 是如何处理的?

如果指向的数组有明确的长度,GCC 将如何处理它?至于“怎么做”,我不认为 GCC 需要做太多的调整。

或者,如果您要询问语义,一旦他们对 VLA 成为一件事感到震惊,他们几乎可以预期它们。指向的对象是一个数组,在任何给定的函数调用中其长度由bucketsize 参数的值指定。这可能因调用而异。

这是您的示例代码的扩展版本,用于演示:

void cistore(int bucketsize, int data[][bucketsize])
{
}

int main()
{
    int d1[5][5];
    int d2[4][6];
    int (*d3)[42] = NULL;

    cistore(5, d1);
    cistore(6, d2);
    cistore(42, d3);

    return 0;
}

【讨论】:

    【解决方案2】:

    变量data是一个variable length array,声明为函数参数时不一定需要有一个整数常量大小:

    如果表达式不是整数常量表达式,则声明符用于可变大小的数组。

    实际上,大小在函数原型范围内被隐式忽略:

    如果大小为 *,则声明用于未指定大小的 VLA。这样的声明只能出现在函数原型范围内,并且声明了一个完整类型的数组。事实上,函数原型范围内的所有 VLA 声明符都被视为表达式被替换为 *。

    (“表达式”通常在方括号之间并指定大小。)

    换句话说,在函数原型中,根本不需要知道大小,仍然认为类型是完整的。只有在函数定义中才需要将大小指定为一些非常量整数表达式。在对函数的调用中,整数表达式可能是 const 或 non-const 我想这(有效地)与在循环中声明和定义 VLA 时类似,即:

    每次控制流通过声明时,都会计算表达式(并且它必须始终计算为大于零的值),并分配数组(相应地,VLA 的生命周期当声明超出范围时结束)。

    (强调我的。)

    ...除了现在在函数调用时评估大小。

    在您的特定情况下,您基本上有一个可变修改类型

    可变长度数组和从它们派生的类型(指向它们的指针等)通常称为“可变修改类型”(VM)。任何可变修改类型的对象只能在块范围或函数原型范围内声明。

    是的,代码确实可以编译。一个例子:

    #include <inttypes.h>
    #include <stdio.h>
    
    struct data_el
    {
        int n;
    };
    
    // Function prototype. Not specifying a size is allowed
    void cistore(uint32_t bucketsize, struct data_el data[][*]);
    
    // Function definition.
    // Must specify the VLA's size as a non-constant integer expression
    void cistore(uint32_t bucketsize, struct data_el data[][bucketsize])
    {
        (*data)[1].n = 5;
        printf("%d\n", (*data)[1].n);
    }
    
    int main(void)
    {
        // Function call. Use any (const or non-const) integer expression.
        uint32_t bucketsize = 2U;
        struct data_el data[bucketsize];
        cistore(bucketsize, &data);
    }
    

    有趣的是,Clang 编译它时没有警告,而 GCC 会警告类型冲突:

    vlaquestion.c:14:50: warning: argument 2 of type ‘struct data_el[][bucketsize]’ declared as a variable length array [-Wvla-parameter]                                                                        
       14 | void cistore(uint32_t bucketsize, struct data_el data[][bucketsize])                                                                                                                                 
          |                                   ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~                                                                                                                                  
    vlaquestion.c:10:50: note: previously declared as an ordinary array ‘struct data_el[][0]’                                                                                                                    
       10 | void cistore(uint32_t bucketsize, struct data_el data[][*]);                                                                                                                                         
          |                                   ~~~~~~~~~~~~~~~^~~~~~~~~
    

    最后,一些关于有争议的 VLA 的有用读物:Why aren't variable-length arrays part of the C++ standard? 在某些时候,甚至努力使 Linux 内核 VLA 免费。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-03
      • 1970-01-01
      • 2022-08-17
      • 2016-01-23
      • 1970-01-01
      • 2022-01-18
      • 1970-01-01
      • 2011-01-17
      相关资源
      最近更新 更多