9

如何将可变长度数组声明为全局变量?

在扫描长度之前在函数中声明可变长度数组时,它会编译但不会运行。它给出了分段错误。当相同的声明语句移到扫描语句下方时,它运行良好。

如果我们想要一个对所有函数全局可用的可变长度数组,我们该怎么做呢?这里的问题是数组的长度只能通过某些函数来扫描。

4

4 回答 4

4

可变长度数组(即使用运行时值调整大小的数组)不能是全局变量,因为您用于大小的表达式显然必须在编译时计算。它只能存在于堆栈中。大概你得到的是一个静态数组,其大小取决于你在代码中定义它的位置(因为你正在重新定义它所依赖的东西)。

为什么不能只使用全局指针和 realloc() 来根据需要调整大小?

于 2012-04-28T03:52:11.730 回答
3

你不能那样做。以下是标准草案的内容:

6.7.6.2 数组声明符

2 如果一个标识符被声明为具有可变修改类型,它应该是一个普通标识符(如 6.2.3 中定义的),没有链接,并且具有块范围或函数原型范围。如果标识符被声明为具有静态或线程存储持续时间的对象,则它不应具有可变长度数组类型。

还,

10 示例 4 所有可变修改 (VM) 类型的声明必须在块范围或函数原型范围内。使用 _Thread_local、static 或 extern 存储类说明符声明的数组对象不能具有可变长度数组 (VLA) 类型。但是,使用静态存储类说明符声明的对象可以具有 VM 类型(即,指向 VLA 类型的指针)。最后,使用 VM 类型声明的所有标识符都必须是普通标识符,因此不能是结构或联合的成员。

于 2012-04-28T03:55:50.093 回答
2

没有办法在 C 中将可变长度数组声明为全局变量,因为必须在知道它的大小之前分配它,因此编译器无法知道它应该为它分配多少内存。但是,您可以(并且应该)做的是动态分配它:

char* my_dynamic_array = NULL;

void f(unsigned int size)
{
    if(!my_dynamic_array) {
        my_dynamic_array = malloc(size);
    }
    /* do something with the array */
}

int main(void)
{
    f(1024); /* set size dynamically */
    /* do something with the array */
    free(my_dynamic_array); /* free the allocated memory */
    return 0;
}
于 2012-04-28T03:56:46.603 回答
1

开张 7 年后,哼哼回答了这个问题。与迄今为止的回答相反,敢于冒险的恶魔有希望:)。

我遇到了这种需求,在线程应用程序中共享全局 VLA(dyn 数组等)。简短的故事,我需要我的线程共享一个全局数组,我把同步/缓存问题放在一边,因为我只是想展示如何共享 VLA,这个例子可以派生用于其他需求(例如外部 VLA , ETC...)

这是代码,后面是解释它为什么工作的注释。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int gn, gm, *ga;                                                 /* (*2) */

void worker(int tndx, long n, long m, int a[n][m])               /* (*6) */
{ long *np=&n, *mp=&m, i=mp-np, *ap=mp+i;                        /* (*7) */

  *ap=(long)ga;
  /* At this oint the worker can elegantly access the global dyn array.
   * elegantly mean through a[i][j].
   */
  printf("worker %d started\n", tndx);
  for(int j=0;j<m;j++)                        
  { a[tndx][j]+=(tndx*1000);                                     /* (*8) */
  }
}

void *init_thread(void *v)
{ int x[1][1], tndx = (int)(long)v;                              /* (*4) */
  printf("thread #%d started\n", tndx);
  worker(tndx, (long)gn, (long)gm, x);                           /* (*5) */
  return(0);
}

int main(int ac, char **av)
{ int n=atoi(av[1]), m=atoi(av[2]);
  pthread_t tt[n];  /* thread table */                           /* (*1) */
  int a[n][m];      /* dyn array    */                           /* (*1) */

  gn=n, gm=m, ga=&a[0][0]; /* globals setup shared by workers */ /* (*2) */
  for(int i=0, k=0;i<n;i++)for(int j=0;j<m;j++)a[i][j]=k++;      /* (*3) */

  printf("Init  a[][]\n");
  for(int i=0, k=0;i<n;i++)for(int j=0;j<m;j++)
    printf("a[%d][%d]=%d\n",i,j,a[i][j]);
  for(int i=0;i<n;i++)
  { if(pthread_create(&tt[i], NULL, init_thread, (void *)(long)i))
    { exit((printf("pthread_create %d failed\n",i),1));
    }
  }
  printf("Draining threads\n");
  for(int i=0;i<n;i++)
  { pthread_join(tt[i],0);
  }
  printf("Final a[][]\n");
  for(int i=0, k=0;i<n;i++)for(int j=0;j<m;j++)
    printf("a[%d][%d]=%d\n",i,j,a[i][j]);
  pthread_exit(NULL);
}

(*1) 这里我们声明 VLA,runstring 将指示线程数,以及我们的 2 个暗淡 VLA 的大小,n 行(每个线程 1 个),每行有 m 个值。

(*2) 我们声明(en setup)我们的全局 VLA,我们公开我们的全局 n 和 m(作为 gn,gm)和我们的全局数组作为指向数组标量类型(这里是 int)的指针,我们初始化它指向一个[0][0]。

(*3) 我们在 a[n][m] 中设置值(连续的 int, 0, 1, 2, ...)

(*4) 每个线程都从 init_thread() 开始,注意我们声明了一个与我们的 a[n][m] VLA 相同类型的虚拟数组,这里的目的是传递一个符合我们的 worker() 的数组API。

(*5) 我们的 worker 需要一个 long 类型的 n, m (dim) 这在 (*6) 中进行了解释,所以这里我们将全局 n 和 m 传递给我们的工作和虚拟数组,我们不在乎关于它,唯一的目的是将数组 addr 作为参数传递。

(*6) 工作 API,我们有一些 args(如 tndx)然后我们有一个 VLA,用 long, n, long m, int a[n][m] 表示。此时 a[][] 是 x[][] 并且没有用。

我们故意使用 long for n 和 m 来修复可能发生的一些堆栈对齐意外,然后将 n、m 和 a 粘合在一起,因为我们取 n 和 m 的 addr,以及寄存器中的 args(现代拱门)被转储到堆栈中的占位符中,i=mp=np 负责定义堆栈方向(arg0,arg1,arg2)此时我们能够访问 x[][] 基本地址并放置我们的全局 ga在那里 *ap=(long)ga;

(*8) 现在我们的工作可以优雅地访问全局(共享)VLA。

这是一个运行

VY$ cc -o t2 t2.c -lpthread

VY$ ./t2 3 4
Init  a[][]
a[0][0]=0
a[0][1]=1
a[0][2]=2
a[0][3]=3
a[1][0]=4
a[1][1]=5
a[1][2]=6
a[1][3]=7
a[2][0]=8
a[2][1]=9
a[2][2]=10
a[2][3]=11
thread #0 started
worker 0 started
thread #2 started
worker 2 started
thread #1 started
worker 1 started
Draining threads
Final a[][]
a[0][0]=0
a[0][1]=1
a[0][2]=2
a[0][3]=3
a[1][0]=1004
a[1][1]=1005
a[1][2]=1006
a[1][3]=1007
a[2][0]=2008
a[2][1]=2009
a[2][2]=2010
a[2][3]=2011

每个线程都修改了它的行,我添加了它的 ID*1000。

因此,我们绝对可以在全球范围内定义 VLA。

VLA 很酷,不需要学习者阅读 alloca() 等内容,但需要全局的,正如在编译时解释的那样,这是不可能的,仍然 GCC(libgcc?)应该能够提供 API在运行时“修补”一个 VLA 基本地址。

我现在很多人会反对 arg addr 获取、堆栈方向 hack 等,但这是许多其他代码的工作方式,va_args、alloca 等......所以它可能看起来很丑,但这种丑陋可能会被隐藏。

干杯,菲

于 2019-07-07T16:34:14.653 回答