C中的动态与静态数组 [英] Dynamic vs static array in c

查看:106
本文介绍了C中的动态与静态数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下代码使用malloc函数创建一个数组.但我知道,仅使用int array [size]即可完成此操作.我认为这是静态数组.但是有了malloc函数,它是动态数组吗?我在网上找到了这段代码...实际情况是什么,静态数组和动态数组(以及堆内存之间的静态内存)有什么区别.您可以在运行时更改动态数组的大小吗?或...我不完全知道...如果有人可以解释,我将不胜感激:)

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

int main(void)
{
    int size;
    int i;
    printf("Choose size of array: ");
    scanf("%d",&size);

    /*---MALLOC/FREE---*/
    int *m_array = (int *)malloc((size+1)*sizeof(int));
    memset(m_array,'\0',size+1);

    for(i=0; i<size ; i++)
    { 
        m_array[i]=i;
        printf("%d ",m_array[i]);
    }
    printf("\n");
    free(m_array);

    return 0;
}

解决方案

有几种类型的数组,具体取决于声明它们的方式和位置.

定长数组

定长数组的大小必须在编译时确定.定义固定长度数组后,您将无法再更改其大小.

固定长度的数组以下列方式之一声明:

T a[N];
T a[N] = { /* initializer list */ };
char_type a[N] = "string literal";
T a[]  = { /* initializer list */ };
char_type a[]  = "string literal";

在前三种情况下,N必须是一个常量表达式,其值必须在编译时知道.在前三种情况下,数组的大小取自N;在后两种情况下,它是从初始化程序列表中的元素数量或字符串文字的大小中获取的.

固定长度数组的初始内容取决于其存储持续时间以及是否已提供初始化程序.

如果数组具有static的存储持续时间(这意味着它是在任何函数体之外的文件范围中声明的,或者是通过static关键字声明的)并且不存在初始化程序,则所有数组元素都将被初始化到0(对于标量)或NULL(对于指针).如果T是聚合类型,例如struct或数组类型,则聚合的每个成员都用0NULL初始化. union类型类似地被清零.

如果数组具有auto个存储持续时间(这意味着它是在没有static关键字的函数或块中声明的)并且不存在初始化程序,则该数组的内容为 indeterminate -基本上是垃圾

如果使用初始化列表(无论存储时间长短)声明数组,则数组元素的初始值将对应于初始化.如果初始化程序中的元素少于数组(例如,N为10,但是您仅初始化了前5个元素),则其余元素被初始化为,就像数组具有储存期限. IOW,给出了声明

int a[10] = {0, 1, 2};

则数组的初始内容为{0, 1, 2, 0, 0, 0, 0, 0, 0, 0}.

包含字符串值的定长数组可以使用字符串文字进行初始化. C允许使用宽"字符串,因此char_type可以是charwchar_t.这些规则与常规的初始化程序列表相同,不同之处在于N(如果已指定)必须至少比字符串的长度大1以说明字符串终止符.

这意味着

char a[10] = "test"; 

将初始化为{'t', 'e', 's', 't', 0, 0, 0, 0, 0, 0}

char a[] = "test";

将初始化为{'t', 'e', 's', 't', 0}.

存储持续时间为static

数组,以便在程序加载后立即可用,并且直到程序退出后才释放它们.这通常意味着它们存储在.data.bss之类的内存段中(或等同于系统使用的任何可执行格式).

存储持续时间为auto

数组,以便在块或函数入口处分配它们,并在块或函数出口处释放它们(实际上,可能会在函数入口处分配它们,而不管它们是否受限制)到函数中较小的范围)-尽管通常没有,但这通常会转换为堆栈.

可变长度数组

在C99中添加了可变长度数组-它们的行为主要与固定长度数组类似,只是它们的大小是在运行时确定的; N不必是编译时常量表达式:

int n;
printf( "gimme the array size: ");
scanf( "%d", &n );
T a[n]; // for any type T

与其名称所暗示的相反,定义了可变长度数组后,您无法再更改其大小. 可变长度"仅表示大小在编译时不固定,并且可以随定义而变化.

由于直到运行时才设置大小,因此不能在文件范围内或使用static关键字声明可变长度数组,也不能使用初始化列表进行声明.确切地如何管理VLA的空间取决于实施.它可能是(通常是)从堆栈中获取的,但AFAIK可能是从其他地方获取的.

动态阵列

至少就我们用来管理它们的对象的数据类型而言,动态数组并不是真正的数组".动态数组是使用malloccallocrealloc之一在运行时分配的,并且该存储将一直保留,直到通过调用free将其释放.

T *p = malloc( sizeof *p * N ); // where N may be either a compile-time or
                                // run-time expression
...
free( p );

可以使用realloc库函数调整动态数组的大小,如下所示:

/**
 * If realloc fails, it will return NULL and leave the original array in 
 * place.  We assign the result to a temporary variable so we don't risk
 * losing our only reference to that memory. 
 */
T *tmp = realloc( p, sizeof *p * new_size );  
if ( tmp )                                    
  p = tmp;                                    

虽然数组元素本身的内存是从堆(或任何动态内存池)中获取的,但 pointer变量 p的内存将通过.bss.data段或堆栈中的内容,基于p的存储持续时间(staticauto).

mallocrealloc分配的内存未初始化;该内存的内容将不确定.用calloc分配的内存将初始化为零.

数组与指针

在某些时候,有人会告诉你数组只是一个指针".那个人是不正确的.

声明数组(固定长度或可变长度)时,将为该数组的元素留出足够的存储空间,没有其他;没有为任何元数据(例如数组长度或指向第一个元素的指针)预留存储空间.有了声明

T a[N];

然后存储空间将如下所示:

    +---+
 a: |   | a[0]
    +---+
    |   | a[1]
    +---+
    |   | a[2]
    +---+
     ...
    +---+ 
    |   | a[N-1]
    +---+

除了数组元素本身(或者更确切地说,对象a 是数组的元素)之外,没有对象a,并且表达式a可能不包含成为任务的目标.

但是...

表达式a[i]定义*(a + i);也就是说,给定指针值a,从该地址偏移i elements (不是字节!)并取消引用结果.但是,如果a不是指针,那该如何工作?

像这样-当它是sizeof或一元&运算符的操作数,或者是用作声明中的数组初始化程序的字符串文字时,其类型为 expression N-T"的元素数组将被转换(衰变")为类型为指向T的指针"的表达式,该表达式的值将是数组的第一个元素.

这有几个含义:

  • 表达式a&a&a[0]都将产生相同的 value (数组第一个元素的地址),但 types 将有所不同(分别为T *T (*)[N]T *);
  • 下标运算符[]与数组表达式和指针表达式均能很好地工作(实际上,已定义用于指针表达式);
  • 将数组表达式传递给函数时,实际上传递的是指针值,而不是整个数组.

对于动态数组,情况有所不同.给定行

T *p = malloc( sizeof *p * N );

然后您的存储空间将如下所示:

   +---+
p: |   | ---+
   +---+    |
    ...     |
     +------+
     |
     V
   +---+
   |   | p[0]
   +---+
   |   | p[1]
   +---+
    ...   
   +---+
   |   | p[N-1]
   +---+

在这种情况下,p 与数组分离的对象.因此,&p 不会为您提供与p&p[0]相同的值,并且其类型将为T **,而不是T (*)[N].另外,由于p只是一个指针变量,因此您可以为其分配一个新值(尽管如果这样做的话,如果free并不首先指向它指向的内存,则会造成内存泄漏).

同样,sizeof p 不会表现得像sizeof a;它只会返回指针变量的大小,而不返回指针指向 的已分配内存的大小.

The following code creates an array with the malloc function. But i know that this can be done much simplier with just int array[size]. I think this is static array. But with the malloc function is it dynamic array? I found this code in the net... What is really happening and whats the difference between a static array and a dynamic array(and static memory between heap memory). Can you change the size of the dynamic array during runtime? or... what i dont know exactly... If someone could explain i would appreciate :)

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

int main(void)
{
    int size;
    int i;
    printf("Choose size of array: ");
    scanf("%d",&size);

    /*---MALLOC/FREE---*/
    int *m_array = (int *)malloc((size+1)*sizeof(int));
    memset(m_array,'\0',size+1);

    for(i=0; i<size ; i++)
    { 
        m_array[i]=i;
        printf("%d ",m_array[i]);
    }
    printf("\n");
    free(m_array);

    return 0;
}

解决方案

There are several flavors of arrays, depending on how and where they are declared.

Fixed-length Arrays

Fixed-length arrays must have their size determined at compile time. You cannot change the size of a fixed-length array after it has been defined.

Fixed-length arrays are declared in one of the following ways:

T a[N];
T a[N] = { /* initializer list */ };
char_type a[N] = "string literal";
T a[]  = { /* initializer list */ };
char_type a[]  = "string literal";

In the first three cases, N must be a constant expression whose value must be known at compile time. In the first three cases, the size of the array is taken from N; in the last two cases, it's taken from the number of elements in the initializer list or the size of the string literal.

The initial contents of a fixed-length array depend on its storage duration and whether an initializer has been supplied.

If the array has static storage duration (meaning it was declared at file scope outside of any function body, or was declared with the static keyword) and no initializer is present, then all of the array elements are initialized to 0 (for scalars) or NULL (for pointers). If T is an aggregate type such as a struct or an array type, then each member of the aggregate is initialized with a 0 or NULL. union types are similarly zeroed out.

If the array has auto storage duration (meaning it was declared within a function or block without the static keyword) and no initializer is present, then the contents of the array are indeterminate - basically, garbage.

If the array is declared with an initializer list (regardless of storage duration), then the initial values of the array elements correspond to the initializer. If there are fewer elements in the initializer than the array (for example, N is 10 but you only initialize the first 5 elements), then the remaining elements are initialized as though the array had static storage duration. IOW, given the declaration

int a[10] = {0, 1, 2};

then the initial contents of the array are {0, 1, 2, 0, 0, 0, 0, 0, 0, 0}.

Fixed-length arrays containing string values may be initialized using a string literal. C allows for "wide" character strings, so char_type may be either char or wchar_t. The rules are the same for regular initializer lists, except that N (if specified) must be at least 1 more than the length of the string to account for the string terminator.

This means that

char a[10] = "test"; 

will be initialized as {'t', 'e', 's', 't', 0, 0, 0, 0, 0, 0} and

char a[] = "test";

will be initialized as {'t', 'e', 's', 't', 0}.

Arrays with static storage duration are stored such that they are available as soon as the program is loaded, and aren't released until the program exits. This usually means that they're stored in a memory segment like .data or .bss (or the equivalent for whatever executable format your system uses).

Arrays with auto storage duration are stored such that they are allocated at block or function entry and released at block or function exit (in practice, they'll probably be allocated at function entry, regardless of whether they're limited to a smaller scope within the function) - this typically translates to the stack, although it doesn't have to.

Variable-length Arrays

Variable-length arrays were added in C99 - they behave mostly like fixed-length arrays, except that their size is established at run time; N does not have to be a compile-time constant expression:

int n;
printf( "gimme the array size: ");
scanf( "%d", &n );
T a[n]; // for any type T

Contrary to what their name implies, you cannot change the size of a variable-length array after it has been defined. "Variable-length" simply means that the size isn't fixed at compile time, and can change from definition to definition.

Since their size isn't set until runtime, variable-length arrays may not be declared at file scope or with the static keyword, nor can they be declared with an initializer list. Exactly how the space for VLAs is managed is up to the implementation; it may be (and usually is) taken from the stack, but AFAIK may be taken from somewhere else.

Dynamic Arrays

Dynamic arrays are not really "arrays" as such, at least in terms of the data types of the objects we use to manage them. Dynamic arrays are allocated at runtime using one of malloc, calloc, or realloc, and that storage is held until released with a call to free.

T *p = malloc( sizeof *p * N ); // where N may be either a compile-time or
                                // run-time expression
...
free( p );

A dynamic array may be resized using the realloc library function, like so:

/**
 * If realloc fails, it will return NULL and leave the original array in 
 * place.  We assign the result to a temporary variable so we don't risk
 * losing our only reference to that memory. 
 */
T *tmp = realloc( p, sizeof *p * new_size );  
if ( tmp )                                    
  p = tmp;                                    

While the memory for the array elements themselves is taken from the heap (or whatever dynamic memory pool), the memory for the pointer variable p will be allocated from either a .bss or .data segment or from the stack, based on p's storage duration (static or auto).

Memory allocated with malloc or realloc is not initialized; the contents of that memory will be indeterminate. Memory allocated with calloc will be initialized with zeros.

Arrays vs. Pointers

At some point, somebody is going to tell you that "an array is just a pointer". That person is not correct.

When you declare an array (either fixed- or variable-length), enough storage is set aside for the elements of that array and nothing else; no storage is set aside for any metadata such as the array length or a pointer to the first element. Given the declaration

T a[N];

then the storage will look something like this:

    +---+
 a: |   | a[0]
    +---+
    |   | a[1]
    +---+
    |   | a[2]
    +---+
     ...
    +---+ 
    |   | a[N-1]
    +---+

There is no object a apart from the array elements themselves (or, more properly, the object a is the elements of the array), and the expression a may not be the target of an assignment.

But...

The expression a[i] is defined as *(a + i); that is, given a pointer value a, offset i elements (not bytes!) from that address and dereference the result. But if a is not a pointer, how can that work?

Like this - except when it is the operand of the sizeof or unary & operators, or is a string literal used as an array initializer in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.

This has several implications:

  • The expressions a, &a, and &a[0] will all yield the same value (the address of the first element of the array), but the types of the expressions will be different (T *, T (*)[N], and T *, respectively);
  • The subscript operator [] works equally well with both array expressions and pointer expressions (indeed, it's defined to work on pointer expressions);
  • When you pass an array expression to a function, what you are actually passing is a pointer value, not the entire array;

For dynamic arrays, the situation is different. Given the line

T *p = malloc( sizeof *p * N );

then your storage will look something like this:

   +---+
p: |   | ---+
   +---+    |
    ...     |
     +------+
     |
     V
   +---+
   |   | p[0]
   +---+
   |   | p[1]
   +---+
    ...   
   +---+
   |   | p[N-1]
   +---+

In this case, p is a separate object from the array. Thus, &p won't give you the same value as p and &p[0], and its type will be T ** as opposed to T (*)[N]. Also, since p is just a pointer variable, you can assign a new value to it (although if you do so without freeing the memory it points to first, you'll create a memory leak).

Similarly, sizeof p won't behave like sizeof a; it will simply return the size of the pointer variable, not the size of the allocated memory that the pointer points to.

这篇关于C中的动态与静态数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆