(编辑) 有人可以帮助我理解为什么动态数组的分配基于两个指针(此代码没有任何问题)
int main(int argc, char** argv) {
int i;
int n=3;
int* *t1;
*t1 = (int*) malloc(n*sizeof(int));
for (i = 0 ; i <n ; i++)
{
scanf("%d",(*t1+i));
}
for ( i=0; i<n; i++)
{
printf("%d",*(*t1+i));
}
而我唯一知道的写作是
int* t;
t = (int*) malloc(n*sizeof(int));
那么使用*t 而不是t有什么好处。 谢谢。
你的问题不清楚,因为你没有具体说明你想要实现的目标。稍后在评论中,您指出您需要一个简单的一维数组,但这不包括在您的问题中。让我们介绍这两种情况以及使用指针到数组的情况,并确保您了解基础知识。
要了解动态分配,您必须了解不涉及数组(例外情况是为固定大小的指针数组分配)。
否则,动态分配包括分配给定大小的内存块,以及将该新内存块的起始地址分配给指针。在C中,不需要投malloc
的回归,这是不必要的。请参阅:我是否投射了 malloc 的结果?(C++为必填项)
为整数分配内存
当您想要分配一个内存块来存储整数时,您只需分配一个足够大的块来容纳要存储的整数数,并将新内存块的起始地址分配给指针。让我们一步一步来:
int *t;
声明一个指针指向int
,分配的内存块的起始地址可以分配给该指针。
int n = 100;
t = malloc (n * sizeof *t);
malloc
分配一个新的n * sizeof *t
字节内存块,该内存块的大小足以容纳n
整数值,并且该块的起始地址分配给指针t
。使用取消引用的sizeof *t
设置类型大小可确保类型大小正确。(t
是指向int
的指针,当您取消引用t
(例如*t
),你的类型是普通的int
,所以sizeof *t
相当于sizeof (int)
)
现在看起来可能并不重要(对于普通类型,它没有太大区别),但是随着您处理的对象类型和指针间接级别的增长,错误地设置不正确的类型大小成为处理分配的嵌套结构等的常见问题。这就是为什么建议仅使用取消引用的指针来设置类型大小的原因。
让我们举一个简单的例子,用户int *t_1D;
指针来强调它是单个线性分配,可以索引为一个简单的一维数组:
#define NUMINT 50 /* number of integers for t_1D */
...
int main (void) {
int *t_1D = NULL; /* pointer to int */
t_1D = malloc (NUMINT * sizeof *t_1D); /* allocate storage for NUMINT int */
if (t_1D == NULL) { /* validate EVERY allocation, handle error */
perror ("malloc-t_1D");
return 1;
}
for (int i = 0; i < NUMINT; i++)
t_1D[i] = i + 1; /* assign a value to each integer */
puts ("nvalues in t_1D (with \n every 5th integer)n");
for (int i = 0; i < NUMINT; i++) { /* loop over each value */
if (i && i % ARRSZ == 0) /* simple mod test to output newline */
putchar ('n');
printf (" %3d", t_1D[i]); /* output value at address */
}
puts ("n");
free (t_1D); /* don't forget to free what you allocate */
上面,当您完成该内存块时,您必须free()
该块以防止内存泄漏。了解在程序退出时释放内存main()
分配的琐碎示例。但是,尽早养成良好的习惯很重要,因为您通常会在函数中全部分配内存,如果在函数返回之前未释放内存(或者在调用者返回之前稍后释放内存),您将丢失指向块的指针,并且它不能自由 - 导致内存泄漏。
使用指针到指针模拟 2D 数组
如果您使用指针到指针(例如int **t;
) 作为您的指针,您的分配是一个两步过程。首先,分配一个内存块来保存一定数量的指针(通常将其视为要分配的对象的rows
数)。
然后在第二组分配中,您将分配一个内存块来保存您希望每行存储的每行整数数(通常被认为是整数的col
(列)数),并将每个分配块的起始地址分配给您分配的指针之一 - 按顺序。
结果是一个数据结构,其中您可以分配 1 个内存块来容纳row
数字指针,并分配row
个内存块来容纳col
个整数。然后,您可以访问每个内存位置,就像访问 2D 数组中的值一样。
但是,请注意,由于从指针到指针创建对象需要多个分配,因此需要多次调用free
以释放分配给该对象的内存。
现在让我们看看如何使用它。下面,我们将使用int **t_2D;
作为变量名称来指示我们正在存储一个可以索引为 2D 数组的对象:
#define ARRSZ 5 /* number of integers per-row for t_2D and t_PTA */
...
int **t_2D = NULL; /* pointer to pointer to int */
...
/* a pointer to pointer (allocate pointers, then ints for each pointer) */
t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D); /* allocate 10 pointers */
if (!t_2D) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++)
/* allocate/validate storage for 5 int assign start address to each ptr */
if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) { /* on failure */
while (i--) /* free previously allocated block for int */
free (t_2D[i]);
free (t_2D); /* free pointers */
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_2D[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_2D output by row x col:n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_2D[i][j]); /* output each integer */
putchar ('n');
}
putchar ('n');
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
free (t_2D[i]); /* free integers */
free (t_2D); /* free pointers */
请注意末尾的循环,该循环遍历每个指针,释放整数的存储空间,然后调用free
以释放保存指针本身的内存块。
指针到数组 -- 单一分配、单一自由、二维索引
您会遇到另一种常见的分配,需要解释。您可以使用固定长度的指针到数组(例如int (*t_PTA)[CONST];
),然后在一次调用中为一定数量的固定数组分配存储,并能够将对象作为 2D 数组寻址,就像上面对t_2D
所做的那样。由于只需要单个分配,因此只需要一个释放来释放与对象关联的内存。
(注意:不要混淆普特纳到阵列(例如int (*p)[CONST]
) 带有指针数组(例如int *p[CONST]
),它们是两种不同的类型)
要分配、使用和释放从指针到数组创建的对象,您可以执行以下操作:
/* a pointer to array -- single allocation, single-free, 2D indexing */
int (*t_PTA)[ARRSZ] = NULL; /* pointer to array of int[ARRSZ] */
t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA); /* storage for 50 integers */
if (!t_PTA) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_PTA[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_PTA output by row x col:n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_PTA[i][j]); /* output each integer */
putchar ('n');
}
putchar ('n');
free (t_PTA);
(注意:单个free (t_PTA);
用于释放与对象关联的所有内存的便利性。
现在让我们把它完全放在一个可行的例子中:
#include <stdio.h>
#include <stdlib.h>
#define ARRSZ 5 /* number of integers per-row for t_2D and t_PTA */
#define NUMINT 50 /* number of integers for t_1D */
int main (void) {
int *t_1D = NULL, /* pointer to int */
**t_2D = NULL, /* pointer to pointer to int */
(*t_PTA)[ARRSZ] = NULL; /* pointer to array of int[ARRSZ] */
/* handling simple storage for integers */
t_1D = malloc (NUMINT * sizeof *t_1D); /* allocate storage for NUMINT int */
if (t_1D == NULL) { /* validate EVERY allocation, handle error */
perror ("malloc-t_1D");
return 1;
}
for (int i = 0; i < NUMINT; i++)
t_1D[i] = i + 1; /* assign a value to each integer */
puts ("nvalues in t_1D (with \n every 5th integer)n");
for (int i = 0; i < NUMINT; i++) { /* loop over each value */
if (i && i % ARRSZ == 0) /* simple mod test to output newline */
putchar ('n');
printf (" %3d", t_1D[i]); /* output value at address */
}
puts ("n");
free (t_1D); /* don't forget to free what you allocate */
/* a pointer to pointer (allocate pointers, then ints for each pointer) */
t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D); /* allocate 10 pointers */
if (!t_2D) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++)
/* allocate/validate storage for 5 int assign start address to each ptr */
if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) { /* on failure */
while (i--) /* free previously allocated block for int */
free (t_2D[i]);
free (t_2D); /* free pointers */
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_2D[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_2D output by row x col:n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_2D[i][j]); /* output each integer */
putchar ('n');
}
putchar ('n');
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
free (t_2D[i]); /* free integers */
free (t_2D); /* free pointers */
/* a pointer to array -- single allocation, single-free, 2D indexing */
t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA); /* storage for 50 integers */
if (!t_PTA) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_PTA[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_PTA output by row x col:n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_PTA[i][j]); /* output each integer */
putchar ('n');
}
putchar ('n');
free (t_PTA);
}
编译和运行以接收以下内容的位置:
示例使用/输出
$ ./bin/dynalloc_1_2_pta
values in t_1D (with n every 5th integer)
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_2D output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_PTA output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
内存使用/错误检查
必须使用内存错误检查程序来确保不会尝试访问内存或超出/超出分配块边界的写入,不会尝试读取或基于未初始化值的条件跳转,最后确认释放了已分配的所有内存。
对于Linux来说,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/dynalloc_1_2_pta
==10642== Memcheck, a memory error detector
==10642== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10642== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10642== Command: ./bin/dynalloc_1_2_pta
==10642==
values in t_1D (with n every 5th integer)
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_2D output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_PTA output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
==10642==
==10642== HEAP SUMMARY:
==10642== in use at exit: 0 bytes in 0 blocks
==10642== total heap usage: 14 allocs, 14 frees, 1,704 bytes allocated
==10642==
==10642== All heap blocks were freed -- no leaks are possible
==10642==
==10642== For counts of detected and suppressed errors, rerun with: -v
==10642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认已释放已分配的所有内存,并且没有内存错误。
一口气消化很多东西,但逐个进行,动态分配将开始有意义。如果您有进一步的问题,请告诉我。
在第一种情况下,t
是指向int*
的指针。正确的方法是分配:
int **t;
t = (int**) malloc(n*sizeof(int*));
所以换句话说:它是一个int*
数组,它是int* t[n]
的动态分配版本。
这与第二种情况不同,第二种情况t
是指向int
类型的指针,基本上t
在这种情况下是一个int
数组,就像动态分配的int t[n]
版本。
请澄清。但是,第一个版本不正确。如果要将指针数组分配给数组,则应执行以下操作:
int **t;
t=(int**) malloc(n*sizeof(int*));
将指针数组分配给每个单独的行,然后分配行:
for (int i=0; i<n; ++i){
t[i] = (int*) malloc(n*sizeof(int));
}
*t
在您的第一个代码段中无效,因为它可以访问。您必须先分配**t
。
当您想要分配int*
对象块时,将使用int**
。 在你的例子中,这不是你所做的或确实想要的。 事实上,您没有向t1
分配或分配任何东西,将分配分配给*t1
(即t1[0]
) 无效,因为t1
本身是未知的。
例如,如果您想要一个分配给多个已分配int
数组的指针数组,那么int**
将是指向int
数组的指针数组的合适数据类型:
// Allocate n arrays of m ints
int** t = malloc( sizeof(int*) * n ) ;
for( int i = 0; i < n; i++ )
{
t[i] = malloc( sizeof(int) * m ) ;
}
然后例如t[2][3]
指的是int
的第三个块中的第四个元素。
这不是语法问题;它们不是同一事物的不同形式;它们在语义上是不同的,在你的例子的上下文中,int**
是不恰当的,在语义上是不正确的。