为什么代码不适用于 malloc 但适用于非动态分配?



以下代码来自黑客排名问题 这里我们有一个输入 n 个单词的代码,然后它输入 q 查询,每个查询由一个单词本身组成,这里的输出对应于查询中的单词在原始数组中的出现次数。 正如我在问题标题和评论中问的那样,为什么它不适用于malloc(对于更大的值)而适用于非动态分配?

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
int n,q,count;
char temp[20];
scanf("%d",&n);
char **a=(char **)malloc(n*20*sizeof(char));//malloc fails here,But works for char a[n][20];
for(int i=0;i<n;i++){                       //WHY do i get garbage value with malloc for bigger values??
scanf("%s",a+i);
}
scanf("%d",&q);
while(q--){
count=0;
scanf("%s",temp);
for(int i=0;i<n;i++) if(strcmp(a+i,temp)==0) count++;
printf("%dn",count);
}
return 0;
}

分配的基本问题是你为char**分配哪个是指向什么的单一指针?(保存指针的内存块)因此,当您尝试a + i每次都会前进a_pointer字节数(通常在 x86_64 上为 8 个,在 x86 上为 4 个)。

如果要使用指针到指针到指针char(通常称为双指针),则必须为n指针分配存储,然后为所需的每个 20 个字符块分配存储空间,并按顺序将每个块的起始地址分配给指针。然后,您可以使用a[i]访问每个分配的块,其中每个字符串的开头0 <= i < n8 字节,距离前一个指针x86_64。(不能保证字符串的存储将在内存中连续,但指针会)

或者作为替代方法,您可以使用指向数组的指针char [20]。(例如char (*a)[20];),这将允许您在单个分配中为n个 20 个字符的数组进行分配。在那里,您的类型是指向数组char [20]因此每个字符串a[i]的开头是前一个字符串a[i-1]之后的 20 个字节,并且所有 20 个字符的存储块保证在内存中是顺序的。作为指针到数组的另一个好处,由于只有一个分配,所以只需要一个free()

这两种方法都允许您像在 2D 数组中一样为每个字符编制索引。(例如a[i][j]0 <= i < n0 <= j < 20的地方.

到目前为止,如果您分配相同大小的块,则使用指针到数组的方法最方便。使用指针到指针的好处是,如果仅分配每个字符串所需的存储量。(尽管它确实增加了一些编码复杂性才能正确完成)

要使用指向数组的指针进行分配,您只需要:

char (*a)[20] = malloc (n * sizeof *a); /* allocate for n 20-char blocks */
if (!a) {       /* validate EVERY allocation */
perror ("malloc-a");
return 1;
}

(注:在C中,不需要投malloc的回归,这是不必要的。请参阅:我是否投射了 malloc 的结果?

另请注意,您必须通过检查返回来验证每个分配,就像您必须通过检查返回验证每个用户输入一样(如果您没有从这个答案中得到任何其他内容,请学习这两个概念,否则您将在代码中邀请未定义的行为)。

通过该更改,您的示例将变为:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (void) {
int n, q;
char temp[20];
if (scanf ("%d", &n) != 1) {    /* validate EVERY input */
fputs ("error: invalid integer value 'n'.n", stderr);
return 1;
}
char (*a)[20] = malloc (n * sizeof *a); /* allocate for n 20-char blocks */
if (!a) {       /* validate EVERY allocation */
perror ("malloc-a");
return 1;
}
for (int i = 0; i < n; i++)
if (scanf ("%19s", a[i]) != 1) {    /* VALIDATE */
fprintf (stderr, "error: failed reading word '%d'n", i);
return 1;
}
if (scanf ("%d", &q) != 1) {            /* VALIDATE */
fputs ("error: invalid integer value 'q'.n", stderr);
return 1;
}
while (q--) {
int count = 0;
if (scanf ("%19s", temp) != 1) {    /* VALIDATE */
fputs ("error: reading 'temp'.n", stderr);
return 1;
}
for (int i = 0; i < n; i++)
if (strcmp (a[i], temp) == 0)
count++;
printf ("%d word(s) match '%s'n", count, temp);
}
free (a);       /* free allocated memory */
}

(注意:每个用户输入和分配都经过验证,字符串的每个scanf()转换说明符都使用字段宽度修饰符来保护每个内存块的分配边界)

示例使用/输出

$ ./bin/dynptr2arr
10
my dog has fleas my cat has none happy cat
3
dog
1 word(s) match 'dog'
has
2 word(s) match 'has'
fleas
1 word(s) match 'fleas'

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有 2个责任:(1)始终保留指向内存块起始地址的指针,以便 (2) 当不再需要内存块时可以释放它。

必须使用内存错误检查程序来确保不会尝试访问内存或超出/超出分配块边界的写入,不会尝试读取或基于未初始化值的条件跳转,最后确认释放了已分配的所有内存。

对于Linux来说,valgrind是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/dynptr2arr
==19181== Memcheck, a memory error detector
==19181== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19181== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==19181== Command: ./bin/dynptr2arr
==19181==
10
my dog has fleas my cat has none happy cat
3
dog
1 word(s) match 'dog'
has
2 word(s) match 'has'
fleas
1 word(s) match 'fleas'
==19181==
==19181== HEAP SUMMARY:
==19181==     in use at exit: 0 bytes in 0 blocks
==19181==   total heap usage: 3 allocs, 3 frees, 2,248 bytes allocated
==19181==
==19181== All heap blocks were freed -- no leaks are possible
==19181==
==19181== For counts of detected and suppressed errors, rerun with: -v
==19181== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存,并且没有内存错误。

仔细查看,如果您有其他问题,请告诉我。

最新更新