静态分配-C语言

问题描述

据我所知,C编译器(我正在使用GCC 6)将扫描代码,以便:

  1. 发现语法问题;
  2. 为程序分配内存(静态分配概念);

那么这段代码为什么起作用?

int main(){
    int integers_amount; // each int has 4 bytes
  
    printf("How many intergers do you wanna store? \n");
    scanf("%d",&integers_amount);

    int array[integers_amount];
    printf("Size of array: %d\n",sizeof(array)); // Should be 4 times integer_amount
    
    for(int i = 0; i < integers_amount; i++){
        int integer;
        printf("Type the integer: \n");
        scanf("%d",&integer);
        array[i] = integer;
    }
    
    for(int j = 0; j < integers_amount; j++){
        printf("Integer typed: %d \n",array[j]);
    }
    

    return 0;
}

我的意思是:

C编译器在编译期间如何推断数组的大小?

我的意思是,它已被声明,但尚未通知其值(编译时间)。我真的相信编译器会在编译时分配所需的内存量(以字节为单位)-这实际上是静态分配的概念。

据我所见,仅在用户通知了数组的“大小”之后,才在运行时完成对变量“数组”的分配。正确吗?

我认为动态分配仅用于使用所需的内存(假设我声明了一个大小为10的整数数组,因为我不知道用户将需要在其中保留多少个值,但是最终我只剩下了使用7,所以我浪费了12个字节。

如果在运行时通知了那些字节,那么我只能分配所需的内存。但是,情况似乎并非如此,因为从代码中我们可以看到数组仅在运行时分配。

我可以帮忙了解一下吗?

谢谢。

解决方法

C编译器在编译期间如何推断数组的大小?

这就是所谓的 可变长度数组 ,或者简称为 VLA ,大小是在运行时确定的但这是一个问题,您无法再调整大小。一些编译器甚至会警告您有关此类数组的用法,因为它们存储在堆栈中,堆栈的大小非常有限,因此可能会导致 stackoverflow 。>


据我所知,只有在用户通知了数组的“大小”之后,才在运行时完成对变量“数组”的分配。正确吗?

是的,这是正确的。这就是为什么它们可能很危险的原因,编译器在编译时将不知道数组的大小,因此,如果数组太大,将无济于事。因此,C ++禁止使用VLA。


假设我声明了一个大小为10的整数数组,因为我不知道用户需要在其中保留多少个值,但最终只使用7,所以浪费了12个字节

与固定大小的数组相反,可以在运行时确定可变长度的数组大小,但是在定义其大小时,您将无法再对其进行更改,因为如果您确实设置了动态数组,则可以进行动态内存分配(前面讨论过)。具有所需的确切大小,而不是一个字节。

无论如何,如果您期望使用外部值来设置数组的大小,则很有可能是您需要的大小,如果没有,那么除了提到的动态内存分配外,您无能为力。在任何情况下,浪费的空间都要多于浪费的空间。


我可以帮忙了解一下吗?

我发现与讨论相关的三个概念:

  1. 固定大小的数组, int array[10]

    它们的大小是在编译时定义的,无法调整大小,如果您已经知道它们应该具有的大小,则很有用。

  2. 可变长度数组, int array[size]size是非常数变量:

    它们的大小在运行时定义,但是只能设置一次,如果数组的大小取决于外部值,例如,用户输入或从文件中检索到的某些值,则它们将非常有用

  3. 动态分配的数组: int *array = malloc(sizeof *arr * size),大小可能为常数,也可能不是常数:

    这些将在需要调整数组大小或太大而无法存储在堆栈中(大小有限)时使用。您可以使用realloc在代码中的任何位置更改其大小,这可以简单地调整数组的大小,或者像@Peter reminded一样,可以简单地分配一个新数组并复制旧数组的内容。结束。

,

在函数内部定义的变量(如代码段中的version: '3.7' services: web: ... depends_on: - db db: image: postgres:12.0-alpine restart: always volumes: - postgres_data:/var/lib/postgres/data/ - ./imports/init.sql:/docker-entrypoint-initdb.d/init.sql environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=user - POSTGRES_DB=db_dev volumes: postgres_data: \c db_dev INSERT INTO table1 (field1,field2,field3) VALUES ('value1','value2','value3'); ... 与其他函数一样!))具有“自动”存储持续时间;通常,这意味着它们在“堆栈”上,这是一种先进的后入先出存储的通用概念,该函数在进入和退出功能时会被构建和取消构建。

“堆栈”只是一个地址,用于跟踪可用于函数局部变量的未使用存储的当前沿。编译器会发出代码,以便在输入函数时将其“向前”移动,以适应局部变量的内存需求,并在程序流离开函数时将其“向后”移动(存在双引号,因为堆栈可能会并朝着较小的地址发展)。

通常,这些在函数进入和返回时的堆栈调整是在编译时计算的;毕竟,局部变量在程序代码中都是可见的。但是,原则上,不能阻止程序“即时”更改堆栈指针。早在Unix时代,Unix就利用了这一功能,并提供了一个动态地在堆栈上分配空间的功能,称为arrayThe FreeBSD man page说:“ alloca()函数出现在32V AT&T UNIX版本中”(是released in 1979)。

main的行为与alloca()非常相似,除了在当前函数返回时存储会丢失并且它是通常的堆栈大小限制的基础。

所以答案的第一部分是您的alloca没有静态存储持续时间。在编译时不知道局部变量将驻留的内存(例如,带有局部变量的函数可能完全调用,也可能根本不调用,取决于运行时用户的输入!)。如果是这样,那么您的惊讶将是完全合理的。

答案的第二部分是 alloc是一个可变长度数组,是C编程语言的一项相当新的功能,仅在1999年才添加。它在上声明了一个对象直到运行时才知道其大小的堆栈(导致反范式的结果是array不是编译时间常数!)。

有人可能会说可变长度数组只是array调用周围的语法糖;但是sizeof(array)尽管可以广泛使用,但不是任何标准的一部分。