【问题标题】:Passing a multidimensional array of variable size without VLA在没有 VLA 的情况下传递可变大小的多维数组
【发布时间】:2016-04-06 05:26:53
【问题描述】:

(这是后续to this question。)

我试图了解将多维数组传递给 C 中的函数的“最佳实践”(或实际上任何实践)。当然这取决于应用程序,所以让我们考虑编写一个函数来打印二维数组大小可变。特别是,我对如何在以下代码中编写函数 printArry(_____) 感兴趣。

上面引用的问题中的答案使用 C 的可变长度数组 (VLA) 特性。These are not present 在 C98 标准中是可选的,在 C11 标准中是可选的(它们似乎也不能在 C++11 中编译)。

如果不使用 VLA 功能,如何回答这个问题?传递二维数组似乎是一项相当基本的任务,所以我无法想象没有合理的方法。

void printArry(_____){
    /* what goes here? */
}

int main(void){

    int a1=5;
    int b1=6;
    int a2=7;
    int b2=8;

    int arry1[a1][b1];
    int arry2[a2][b2];

    /* set values in arrays */

    printArry(arry1, a1, b1);
    printArry(arry2, a2, b2);
    return 0;
}

【问题讨论】:

  • 对不起,int arry1[a1][b1]; 等不是 VLA 吗?有了这些,甚至更多需要将数组维度传递给函数,因为您无法定义类型。
  • @Weather Vane,我不是标准专家,但(例如)c++11 接受 int arry1[a1][b1] 的初始化,但不接受 VLA 在函数定义。我认为在 c98 中也是如此,但我目前无法对其进行测试。
  • @ABD 关于作为函数参数传递的数组的唯一信息是它作为指针的基地址。由于函数无法知道其维度,因此您也必须传递它。
  • 在c89中不能写int arry1[a1][b1];等。

标签: c arrays multidimensional-array


【解决方案1】:

问题目前有问题:

上述问题的答案使用了 C 的可变长度数组 (VLA) 特性。这些特性在 C98 标准中不存在,在 C11 标准中是可选的。

C99 (ISO/IEC 9899:1990) 之前的 C 标准是 C89 (ANSI) 或 C90 (ISO) — 功能上相同的标准。有 C++98 (ISO/IEC 14882:1998),但那是一种完全不同的语言。

int main(void)
{
    int a1=5;
    int b1=6;
    int a2=7;
    int b2=8;

    int arry1[a1][b1];
    int arry2[a2][b2];

此示例代码使用 VLA;它不会在严格的 C90 规则下编译。


如果您受限于 1999 年之前的 C 标准,那么您将受限于使用与此类似的代码。显然,您可以通过从文件或其他任何您喜欢的东西中读取数据来初始化数组;使用直接初始化程序是可能的,因为数组不再是 VLA。

#include <stdio.h>

void printArry(int d1, int d2, int *data);

int main(void)
{
    enum { a1 = 5, b1 = 6, a2 = 7, b2 = 8 };
    int arry1[a1][b1] =
    {
        {  9,  8,  7,  6,  5,  4 },
        {  2,  3,  4,  3,  2,  1 },
        { -9, -8, -7, -6, -5, -4 },
        { 39, 38, 37, 36, 35, 34 },
        { 99, 98, 97, 96, 95, 94 },
    };
    int arry2[a2][b2] =
    {
        { 198, 158, 165, 136, 198, 127, 119, 103, },
        { 146, 123, 123, 108, 168, 142, 119, 115, },
        { 160, 141, 168, 193, 152, 152, 147, 137, },
        { 144, 132, 187, 156, 188, 191, 196, 144, },
        { 197, 164, 108, 119, 196, 171, 185, 133, },
        { 107, 133, 184, 191, 166, 105, 145, 175, },
        { 199, 115, 197, 160, 114, 173, 176, 184, },
    };

    printArry(a1, b1, &arry1[0][0]);
    printArry(a2, b2, &arry2[0][0]);

    return 0;
}

void printArry(int d1, int d2, int *data)
{
    int i, j;
    for (i = 0; i < d1; i++)
    {
        for (j = 0; j < d2; j++)
            printf(" %4d", data[i * d2 + j]);
        putchar('\n');
    }
}

printArry() 函数本质上是执行您希望编译器为您执行的索引(下标)计算 - 但它不能,因为它无法处理 C99 VLA。

第一个数据数组是手工制作的,以便发现问题(例如在下标计算中使用d1 而不是d2)。第二组数据只是 100 到 199 之间的 56 个随机值。

样本输出:

    9    8    7    6    5    4
    2    3    4    3    2    1
   -9   -8   -7   -6   -5   -4
   39   38   37   36   35   34
   99   98   97   96   95   94
  198  158  165  136  198  127  119  103
  146  123  123  108  168  142  119  115
  160  141  168  193  152  152  147  137
  144  132  187  156  188  191  196  144
  197  164  108  119  196  171  185  133
  107  133  184  191  166  105  145  175
  199  115  197  160  114  173  176  184

【讨论】:

    【解决方案2】:

    您不需要 VLA 即可将 VLA 作为参数传递。事实上,您必须知道数组和函数既不能传递给函数也不能从函数返回(除非您使用结构 hack,但在我们的例子中这是行不通的)。

    但是,您可以使用 VM 类型将指针传递给您的数组。像这样的:

    void printArry(size_t a1, size_t b1, int (*parry)[a1][b1]){
    /* accessing array 'array' by '*parry' */
    }
    

    出于显而易见的原因,必须在声明定义数组维度的参数后声明指向 VLA 数组参数的指针(使用时必须定义'a1'和'b1')。

    使用此函数的代码如下所示:

    int main(int argc, char** argv){
    
    size_t a1=5;
    size_t b1=6;
    size_t a2=7;
    size_t b2=8;
    
    int arry1[a1][b1];
    int arry2[a2][b2];
    
    /* set values in arrays */
    
    printArry(a1, b1, &arry1);
    printArry(a2, b2, &arry2);
    
    }
    

    我们不能使用结构破解首先是因为结构不能有 VLA 成员,其次是因为即使它们可以,我们也无法在传递参数时指定它们的长度(成员类型必须在声明的时间,我们不能在函数声明中声明结构)。

    已确认 - 即使是普通(非 VM)数组也必须使用此版本的“printArry”(假设它们的尺寸大小已正确传递给函数)。至少这是 C11 标准在它的例子中说。

    所有指向具有相同维度大小的数组的指针都可以在不调用 UB 的情况下相互分配(无论它们是否为 VM)。

    以下是最新 C11 标准的示例:

    示例 3 以下声明演示了兼容性 可变修改类型的规则。

    extern int n;
    
    extern int m;
    
    void fcompat(void)
    {
        int a[n][6][m];
    
        int (*p)[4][n+1];
    
        int c[n][n][6][m];
    
        int (*r)[n][n][n+1];
    
        p = a;   // invalid: not compatible because 4 != 6
    
        r = c;   // compatible, but defined behavior only if
                 // n == 6 and m == n+1
    
    }
    

    6.7.6.2 美元。

    这意味着你可以像这样调用这个函数:

    int main(int argc, char **argv)
    {
        int arry[10][23];
    
        printArry(sizeof(arry) / sizeof(arry[0]), 
                sizeof(arry[0]) / sizeof(arry[0][0]), &arry);
    }
    

    另一方面,您也可以使用指向数组第一个元素的简单指针以旧的方式访问数组:

    void printArry(size_t a1, size_t b1, int *parryFirstElement){
        for(size_t i = 0; i < a1; ++i)
            for(size_t y = 0; y < b1; ++y)
                parryFirstElement[i * b1 + y]; //your array [i][y] element
    }
    

    可以像以前一样调用此函数(因为不需要强制转换 'C' 中的 IIRC 指针)。

    我在使用“size_t”更好的时候替换了“int”。虽然如果你的目标是一些旧标准,你可以将它们反转回'int'。

    我可能错了。我现在很困。如果我错了 - 那就糟糕了。

    【讨论】:

    • 我不了解您提出的功能应该如何工作。 a2和b2的作用是什么?
    • 它们定义了您传递指针的 VLA 的尺寸。但是请稍等,因为我现在正在编辑我的问题。
    • 好的。您可以立即发表评论。我将切换到我的另一台 PC 并在这个时候检查标准(有关更多信息,这里的代码肯定有效)。
    • 我不明白:你对 printArry 的调用有 3 个参数,但函数是用 5 个参数定义的?
    • 谢谢。我想我明白这一点。但这真的不使用 VLA 吗?如果我有 arry1 和 arry2 的静态定义,它会在 c98 中编译(特别是函数 printArry)吗?
    【解决方案3】:

    似乎对标准和 VLA 存在误解:C99 和所有以下 C 标准都包含 VLA,并且编译器,例如 gcc 支持它(-std=c99,@ 987654324@等标志)。

    什么不起作用,是任何标准的C++。 VLA 根本不是该语言的特性,C 在这方面比 C++ 强大得多。

    因此,如果您想使用 VLA,只需确保您使用真正的 C 编译器 (gcc) 进行编译,而不是使用 C++ 编译器 (g++)。如果您这样做,它们应该会像魅力一样发挥作用。


    Afaik,有一个关于 C++17 的提案,它说它想将 VLA 添加到该语言中。但是,该提案中的 VLA 功能受到严重限制。直到我眼中完全无法使用。在这方面,C99 仍然比 C++17 强大得多。

    【讨论】:

    • VLA 不完全是“强大”功能​​。像 C 那样支持 VLA 会破坏 C++ 的许多其他功能——这可能解释了为什么您提到的那个提议会限制它们。 C++ 并不真正需要 VLA,因为它有标准容器,可以递归地相互包含。这更符合 C++ 在更改语言之前使用或向库添加功能的理念(与 C 理念相反)。 C VLA 是否更好取决于偏好,以及在 C++ 中,人们愿意放弃哪些其他功能来实现它们。
    • 从技术上讲,gcc 和 g++ 都不是编译器。
    • @Peter 他们破坏了哪些能力?它们目前被支持作为语言扩展(gcc、clang)。支持它们允许堆栈分配,这可能是一个不平凡的优化。 dynarray 提案明确说明了这一点。
    • 请注意,G++(GCC C++ 编译器)默认允许在 C++ 中使用 VLA;你可以告诉它不要接受它们。
    • @black:好的;我会咬 - gccg++ 不是编译器吗?
    猜你喜欢
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多