C VLA原型和多维数组参数

C VLA原型和多维数组参数,c,arrays,multidimensional-array,variable-length-array,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

我创建了一个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参数或固定大小声明函数之间有什么区别?

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

现在,让我们比较两种变体,考虑到您实际上也需要外部维度来防止在迭代时超过数组边界:

void create_polygon1(int faces[][6]);
void create_polygon2(int n, int faces[][n]);
不管最初传递给的数组是作为经典数组还是作为VLA创建的,第一个函数接受长度正好为6的数组,第二个函数可以接受任意长度的数组,假设到目前为止这是明确的

面[i][j]现在将翻译为:

*((int*)faces + (i * 6 + j)) // (1)
*((int*)faces + (i * n + j)) // (2)
虽然差异看起来很小,但假设所有变量都存储在堆栈中,在汇编程序级别上可能会更明显;假设sizeofint==4:

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

对于由于注释而添加的完整性,与原始问题无关:还有int*array[]案例:通过指向数组的指针数组表示

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

因为我们需要在应用下一个索引之前取消引用数组中的真指针。用于比较的汇编程序代码,首先需要指向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

我反汇编了这个代码:

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
正如Aconcagua所指出的,在使用VLA的第一个示例中,在运行时通过将int的大小乘以第二个维度的大小来计算大小,第二个维度是存储在rsi中的参数,然后移动到edi中


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

为什么不将两个大小传递给函数?传递第一个维度大小是不相关的,在多维数组的情况下,第一级间接寻址作为指向数组的指针传递。是,但是您仍然需要函数中的第一个大小。否则你就不知道要迭代多长时间了。我忘了说第一个维度是3三角形。但问题不是你在回答我什么。我的问题是原型中使用的VLA和固定大小的VLA之间的区别;void gvoid{inta[7];fa;}-当一个指针衰减时,f怎么知道p aka有7个元素?例外情况:如果在e末尾有一个sentinel值。G在字符串中终止0或在指针数组中终止空指针。面[i][j]始终等于**面+i+j是什么意思?对于数组类型,它不等于*int*faces+i*n+j,其中n是内部维度的大小吗?但是传递多维数组类型时有很大的区别,不是吗?这里**faces+i+j相当于**int*[6]faces+i+j,因为数组类型在用作左值时会衰减为指针,*faces+i是指向int的指针。但如果我传递int**faces,它就会崩溃,因为**faces+i+j相当于**int**faces+i+j。一个实际取消引用4*6字节并获取一个数组类型,该数组类型在使用时会衰减为指针,另一个取消引用8字节并获取一个垃圾指针。我说的对吗?@Prion小心:int**是完全不同的东西!!!这种类型数组中的每个元素本身都是指针,而不是数组。当然,每个指针都可以指向一个数组,但不是它本身。当然,C中的访问仍然是faces[i][j],但不能像int*[whatever]那样直接计算偏移量:您需要先计算偏移量i,加载指针地址,然后向其中添加j。请注意,对于真正的2dim数组,您有连续的内存,并且每行的大小必须相同。这两种方法都不一定适用于前者,而实际上不太可能用于int *.@ PrIon问题根本不考虑int,所以我在我的回答中也没有提到…而是数组类型,当使用LV值衰减到指针到它们的第一个元素时,所以脸[0 ]。例如,它是6整数的数组类型,但当用作左值时,它会衰减为指向int的指针。
void    create_polygon(int n, int faces[][6])
{
    int a = sizeof(faces[0]);
    (void)a;
}
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