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


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

那怎么计算呢?

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


10246
2017-12-17 04:51


起源



答案:


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

普通标识符(如6.2.3中所定义),具有可变修改类型 应该有   块范围和没有链接或功能原型范围。 如果声明了标识符   要成为具有静态存储持续时间的对象,它不应具有可变长度的数组类型。

VLA的大小很简单 sizeof(vla_element_type) * vla_length。由于VLA只能在一个块内定义, its length must be either a local variable or a function parameter,当访问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;
}

编译 clang -o test.ll -O2 -emit-llvm -S test.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
}

6
2017-12-17 05:25



可以像这样定义一个函数: foo(int array[size], int size) 这样我就可以用了 sizeof(array) 计算的大小 array 在那个功能里面? - minh.hieu
不,这是不可能的。数组参数将由编译器转换为指针。另请看这篇文章: stackoverflow.com/questions/5493281/c-sizeof-a-passed-array - Lei Mou
好的,GCC / CLANG n00b在这里:什么是IR? - Cole Johnson
IR表示中间表示: en.wikipedia.org/wiki/... - Lei Mou


答案:


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

普通标识符(如6.2.3中所定义),具有可变修改类型 应该有   块范围和没有链接或功能原型范围。 如果声明了标识符   要成为具有静态存储持续时间的对象,它不应具有可变长度的数组类型。

VLA的大小很简单 sizeof(vla_element_type) * vla_length。由于VLA只能在一个块内定义, its length must be either a local variable or a function parameter,当访问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;
}

编译 clang -o test.ll -O2 -emit-llvm -S test.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
}

6
2017-12-17 05:25



可以像这样定义一个函数: foo(int array[size], int size) 这样我就可以用了 sizeof(array) 计算的大小 array 在那个功能里面? - minh.hieu
不,这是不可能的。数组参数将由编译器转换为指针。另请看这篇文章: stackoverflow.com/questions/5493281/c-sizeof-a-passed-array - Lei Mou
好的,GCC / CLANG n00b在这里:什么是IR? - Cole Johnson
IR表示中间表示: en.wikipedia.org/wiki/... - Lei Mou


VLA和a之间的区别 malloc通过指针变量保存的ed数组(除了存在于内存的不同部分之外)是编译器在编译时知道第一个是数组。它可以在VLA的某个位置保存大小信息,所以基本上这是一些隐藏的变量。根据您对该变量的使用情况,如果您使用 sizeof 使用它或者通过类似的东西索引2D VLA A[i][j] 然后,编译器可以确定是否真的需要该隐藏变量,如果不是,则优化它。


5
2017-12-17 07:54



容易明白!谢谢。抱歉11rep我不能为你+1。 - minh.hieu