VLA原型和多维数组参数

时间:2018-12-15 16:33:10

标签: c arrays multidimensional-array variable-length-array

我这样创建了一个C99 VLA函数:

void create_polygon(int n, int faces[][n]);

我想在另一个要分配二维数组的函数中调用此函数:

void parse_faces()
{
    int faces[3][6];

    create_polygon(6, faces);
}

当我将二维数组作为参数传递时,它传递一个指向6整数数组的指针,该指针引用调用函数中的堆栈内存。

此处的VLA参数仅用作类型声明(不分配任何实际内存),告诉编译器使用((int*)faces)[i * 6 + j]而不是faces[i][j]以行优先顺序访问数据。

使用VLA参数或固定大小声明函数之间有什么区别?

2 个答案:

答案 0 :(得分:1)

faces[i][j] 始终等同于*(*(faces + i) + j),无论是否使用VLA。

现在让我们比较两个变体(不考虑实际上还需要外部尺寸以防止在迭代时超出数组范围):

void create_polygon1(int faces[][6]);
void create_polygon2(int n, int faces[][n]);

传递给原始数组是作为经典数组还是作为VLA创建都没有关系,第一个函数接受长度恰好为6的数组,第二个函数可以接受任意长度的数组(假设到目前为止很清楚……)。

faces[i][j]现在将被翻译为:

*((int*)faces + (i * 6 + j)) // (1)
*((int*)faces + (i * n + j)) // (2)

差异看起来仍然很有限,但在汇编程序级别上可能会更加明显(假设所有变量尚未存储在堆栈中;假设sizeof(int) == 4):

LD     R1, i;
LD     R2, j;
MUL    R1, R1, 24; // using a constant! 24: 6 * sizeof(int)!
MUL    R2, R2, 4;  // sizeof(int)
ADD    R1, R2, R2; // index stored in R1 register

LD     R1, i;
LD     R2, j;
LD     R3, m;      // need to load from stack
MUL    R3, R3, 4;  // need to multiply with sizeof(int) yet     
MUL    R1, R1, R3; // can now use m from register R3
MUL    R2, R2, 4;  // ...
ADD    R1, R2, R2; // ...

当然,正确的汇编代码可能会有所不同,特别是如果您使用允许在寄存器中传递某些参数的调用约定(那么可能不需要将n加载到R3中)。


为了完整性(由于注释而添加,与原始问题无关):
还有int* array[]情况:用指向数组的指针的数组表示。

*((int*)faces + (i * ??? + j))

不再起作用,因为在这种情况下faces没有连续的内存(当然,指针本身在连续的内存中,但是不是所有的faces[i][j])。我们被迫这样做:

*(*(faces + i) + j)

,因为我们需要在应用下一个索引之前先取消引用数组中的true指针。汇编程序代码(为进行比较,首先需要一个指向2D数组的更完整的指针变体):

LD     R1, faces;
LD     R2, i;
LD     R3, j;
LD     R4, m;      // or skip, if no VLA
MUL    R4, R4, 4;  // or skip, if no VLA
MUL    R2, R2, R3; // constant instead of R3, if no VLA
MUL    R3, R3, 4;
ADD    R2, R2, R3; // index stored in R1 register
ADD    R1, R1, R2; // offset from base pointer
LD     R1, [R1];   // loading value of faces[i][j] into register

LD     R1, faces;
LD     R2, i;
LD     R3, j;
MUL    R2, R2, 8;  // sizeof(void*) (any pointer)
MUL    R3, R3, 4;  // sizeof(int)
ADD    R1, R1, R2; // address of faces[i]
LD     R1, [R1];   // now need to load address - i. e. de-referencing faces[i]
ADD    R1, R1, R3; // offset within array
LD     R1, [R1];   // loading value of faces[i][j] into register

答案 1 :(得分:0)

我反汇编了这段代码:

void    create_polygon(int n, int faces[][6])
{
    int a = sizeof(faces[0]);
    (void)a;
}

带有VLA参数:

movl    %edi, -4(%rbp)   # 6
movq    %rsi, -16(%rbp)  # faces
movl    %edi, %esi
shlq    $2, %rsi         # 6 << 2 = 24
movl    %esi, %edi

固定大小:

movl    %edi, -4(%rbp)
movq    %rsi, -16(%rbp)
movl    $24, %edi        # 24

正如阿孔卡瓜所指出的,在使用VLA的第一个示例中,大小是在运行时通过将int的大小乘以第二个维度的大小来计算的,第二个维度是存储在{{ 1}},然后移至rsi

在第二个示例中,大小是在编译时直接计算的,并放入edi中。主要优点是能够在传递不同大小的情况下检查不正确的指针类型参数,从而避免崩溃。