我创建了一个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参数或固定大小声明函数有什么区别?
发布于 2018-12-15 17:51:36
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)因为我们需要取消引用数组中的真正指针,然后才能应用下一个索引。汇编程序代码(为了比较,首先需要一个指向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发布于 2018-12-16 00:02:43
我分解了这个代码:
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正如Aconcagua所指出的,在使用VLA的第一个示例中,在运行时通过将int的大小乘以存储在rsi中的参数的第二个维度的大小来计算大小,然后移动到edi中。
在第二个示例中,在编译时直接计算大小并将其放置到edi中。其主要优点是,如果传递不同的大小,则可以检查不正确的指针类型参数,从而避免崩溃。
https://stackoverflow.com/questions/53795107
复制相似问题