如何将可变长度数组声明为全局变量?
在扫描长度之前在函数中声明可变长度数组时,它会编译但不会运行。它给出了分段错误。当相同的声明语句移到扫描语句下方时,它运行良好。
如果我们想要一个对所有函数全局可用的可变长度数组,我们该怎么做呢?这里的问题是数组的长度只能通过某些函数来扫描。
如何将可变长度数组声明为全局变量?
在扫描长度之前在函数中声明可变长度数组时,它会编译但不会运行。它给出了分段错误。当相同的声明语句移到扫描语句下方时,它运行良好。
如果我们想要一个对所有函数全局可用的可变长度数组,我们该怎么做呢?这里的问题是数组的长度只能通过某些函数来扫描。
可变长度数组(即使用运行时值调整大小的数组)不能是全局变量,因为您用于大小的表达式显然必须在编译时计算。它只能存在于堆栈中。大概你得到的是一个静态数组,其大小取决于你在代码中定义它的位置(因为你正在重新定义它所依赖的东西)。
为什么不能只使用全局指针和 realloc() 来根据需要调整大小?
你不能那样做。以下是标准草案的内容:
6.7.6.2 数组声明符
2 如果一个标识符被声明为具有可变修改类型,它应该是一个普通标识符(如 6.2.3 中定义的),没有链接,并且具有块范围或函数原型范围。如果标识符被声明为具有静态或线程存储持续时间的对象,则它不应具有可变长度数组类型。
还,
10 示例 4 所有可变修改 (VM) 类型的声明必须在块范围或函数原型范围内。使用 _Thread_local、static 或 extern 存储类说明符声明的数组对象不能具有可变长度数组 (VLA) 类型。但是,使用静态存储类说明符声明的对象可以具有 VM 类型(即,指向 VLA 类型的指针)。最后,使用 VM 类型声明的所有标识符都必须是普通标识符,因此不能是结构或联合的成员。
没有办法在 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;
}
开张 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 等......所以它可能看起来很丑,但这种丑陋可能会被隐藏。
干杯,菲