如何在C99中运行时计算可变长度数组的大小?

时间:2022-01-21 03:12:57

In C89, the length of an array is known at compile time. But in C99, with variable length arrays, the length of an array may be unknown before runtime.

在C89中,数组的长度在编译时是已知的。但是在C99中,对于可变长度数组,在运行之前数组的长度可能是未知的。

So how does it get computed?

那么它是如何计算的呢?

And why couldn't the length of a dynamically allocated array be computed in the same way?

为什么动态分配的数组的长度不能以相同的方式计算?

2 个解决方案

#1


6  

From ISO/IEC 9899:TC3 Section 6.7.5.2: Array declarators

来自ISO/ iec9899:TC3第6.7.5.2节:数组声明符

An ordinary identifier (as defined in 6.2.3) that has a variably modified type shall have either block scope and no linkage or function prototype scope. If an identifier is declared to be an object with static storage duration, it shall not have a variable length array type.

普通标识符(如6.2.3中定义的)具有可变修改类型的标识符应该具有块作用域,而没有链接或函数原型作用域。如果标识符被声明为具有静态存储持续时间的对象,则它不应该具有可变长度数组类型。

The sizeof a VLA is simply sizeof(vla_element_type) * vla_length. Since a VLA can only be defined within a block, its length must be either a local variable or a function parameter, which can be accessed by the compiler when the vla is accessed. (Since the length of vla and the vla itself belongs to the same stack frame).

VLA的大小就是sizeof(vla_element_type) * vla_length。由于VLA只能在块中定义,所以它的长度必须是局部变量或函数参数,在访问VLA时编译器可以访问这些参数。(因为vla的长度和vla本身属于同一个堆栈帧)。

Here is an example:

int main(int argc, char* argv[])
{
  int m;
  scanf("%d\n", &m);
  int a[m];

  printf("%d\n", sizeof(a));

  return 0;
}

Compiled with clang -o test.ll -O2 -emit-llvm -S test.c, the generated IR is shown as follows:

用clang -o测试编译。ll -O2 -emit-llvm -S测试。c,生成的IR如下所示:

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  // Allocate space on stack for m
  %m = alloca i32, align 4  

  // call scanf
  %call = call i32 (i8*, ...)* @__isoc99_scanf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32* %m) nounwind  

  // %0 now contains the value of m
  %0 = load i32* %m, align 4, !tbaa !0

  // %1 is m << 2, which is m * sizeof(int)
  %1 = shl nuw i32 %0, 2  

  // call printf, output m * sizeof(int) to screen.
  %call1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %1) nounwind  

  // DONE.
  ret i32 0
}

#2


5  

The difference between a VLA and a malloced array that you hold through a pointer variable (besides living in different parts of the memory) is that the compiler knows at compile time that the first is an array. It can hold the size information(s) in some place along with the VLA, so basically this is some sort of hidden variable(s). Depending on the usage that you do with that variable, if e.g you use sizeof with it or if you index a 2D VLA through something like A[i][j] the compiler can then decide if that hidden variable is really needed, and, if not, optimize it out.

VLA和通过指针变量(除了驻留在内存的不同部分之外)保存的不正确数组之间的区别在于编译器在编译时知道第一个是数组。它可以把大小信息和VLA放在一起,所以基本上这是某种隐藏的变量。取决于你对这个变量的用法,如果e。g你可以使用sizeof,或者如果你用[i][j]之类的索引一个2D VLA,编译器就可以决定是否真的需要那个隐藏的变量,如果不是,优化它。

#1


6  

From ISO/IEC 9899:TC3 Section 6.7.5.2: Array declarators

来自ISO/ iec9899:TC3第6.7.5.2节:数组声明符

An ordinary identifier (as defined in 6.2.3) that has a variably modified type shall have either block scope and no linkage or function prototype scope. If an identifier is declared to be an object with static storage duration, it shall not have a variable length array type.

普通标识符(如6.2.3中定义的)具有可变修改类型的标识符应该具有块作用域,而没有链接或函数原型作用域。如果标识符被声明为具有静态存储持续时间的对象,则它不应该具有可变长度数组类型。

The sizeof a VLA is simply sizeof(vla_element_type) * vla_length. Since a VLA can only be defined within a block, its length must be either a local variable or a function parameter, which can be accessed by the compiler when the vla is accessed. (Since the length of vla and the vla itself belongs to the same stack frame).

VLA的大小就是sizeof(vla_element_type) * vla_length。由于VLA只能在块中定义,所以它的长度必须是局部变量或函数参数,在访问VLA时编译器可以访问这些参数。(因为vla的长度和vla本身属于同一个堆栈帧)。

Here is an example:

int main(int argc, char* argv[])
{
  int m;
  scanf("%d\n", &m);
  int a[m];

  printf("%d\n", sizeof(a));

  return 0;
}

Compiled with clang -o test.ll -O2 -emit-llvm -S test.c, the generated IR is shown as follows:

用clang -o测试编译。ll -O2 -emit-llvm -S测试。c,生成的IR如下所示:

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
entry:
  // Allocate space on stack for m
  %m = alloca i32, align 4  

  // call scanf
  %call = call i32 (i8*, ...)* @__isoc99_scanf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32* %m) nounwind  

  // %0 now contains the value of m
  %0 = load i32* %m, align 4, !tbaa !0

  // %1 is m << 2, which is m * sizeof(int)
  %1 = shl nuw i32 %0, 2  

  // call printf, output m * sizeof(int) to screen.
  %call1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %1) nounwind  

  // DONE.
  ret i32 0
}

#2


5  

The difference between a VLA and a malloced array that you hold through a pointer variable (besides living in different parts of the memory) is that the compiler knows at compile time that the first is an array. It can hold the size information(s) in some place along with the VLA, so basically this is some sort of hidden variable(s). Depending on the usage that you do with that variable, if e.g you use sizeof with it or if you index a 2D VLA through something like A[i][j] the compiler can then decide if that hidden variable is really needed, and, if not, optimize it out.

VLA和通过指针变量(除了驻留在内存的不同部分之外)保存的不正确数组之间的区别在于编译器在编译时知道第一个是数组。它可以把大小信息和VLA放在一起,所以基本上这是某种隐藏的变量。取决于你对这个变量的用法,如果e。g你可以使用sizeof,或者如果你用[i][j]之类的索引一个2D VLA,编译器就可以决定是否真的需要那个隐藏的变量,如果不是,优化它。