我有一个文件,它有num
行:每行包含一个数字。我想把每个数字都保存到一个向量*vet
中。这两个版本中哪一个更好?
[版本1]我有两个功能:第一个用于计算num
,第二个用于将数字保存到*vet
中。我在main()
中用malloc
分配内存。
#include <stdio.h>
#include <stdlib.h>
/* The first function counts lines number */
int count_line (int *num)
{
FILE *fin;
char buff[10];
*num = 0;
if ( !(fin = fopen("numbers.dat", "r")) )
return 1;
while ( fgets(buff, sizeof(buff), fin) )
(*num)++;
return fclose(fin);
}
/* The second function save numbers into a vector */
int save_numbers (int *vet)
{
FILE *fin;
int i=0;
char buff[10];
if ( !(fin = fopen("numbers.dat", "r")) )
return 1;
while ( fgets(buff, sizeof(buff), fin) )
{
sscanf (buff, "%d", &vet[i]);
i++;
}
return fclose(fin);
}
int main ()
{
int num, i, *vet;
if ( count_line(&num) )
{
perror("numbers.dat");
exit(1);
}
vet = (int *) malloc ( num * sizeof(int) );
if ( save_numbers(vet) )
{
perror("numbers.dat");
exit(2);
}
/* print test */
for (i=0; i<num; i++)
printf ("%d ", vet[i]);
printf("n");
free(vet);
return 0;
}
[版本2]我只有一个功能:它用realloc
分配内存,并将数字保存到*vet
中。
#include <stdio.h>
#include <stdlib.h>
/* This function allocate memory
and save numbers into a vector */
int save_numbers (int **vet, int *num)
{
FILE *fin;
int i = 0;
char buff[10];
if ( !(fin = fopen("numbers.dat", "r")) )
return 1;
while ( fgets(buff, sizeof(buff), fin) )
{
*vet = (int *) realloc (*vet, (i+1) * sizeof(int) );
sscanf (buff, "%d", &(*vet)[i]);
i++;
}
*num = i;
return fclose(fin);
}
int main ()
{
int i, num, *vet = NULL;
if ( save_numbers(&vet, &num) )
{
perror("numbers.dat");
exit(1);
}
/* print test */
for (i=0; i<num; i++)
printf ("%d ", vet[i]);
printf("n");
free(vet);
return 0;
}
此处的文件示例:http://pastebin.com/uCa708L0
正如一个人所评论的,磁盘I/O非常昂贵,所以版本2更好,因为它只读取一次文件。
不过这并不好;一般来说,增量内存分配的成本是二次方的。您应该计划在每次分配时将分配的空间量增加一倍,以摊销分配成本。这避免了在为数字N分配空间时将以前的N-1个数字从一个位置复制到下一个位置的成本。这种情况并不总是发生,但在形式上,realloc()
会释放当前分配的空间并分配新的空间(但有时新旧指针会相同)。
#include <stdio.h>
#include <stdlib.h>
/* This function allocate memory
and save numbers into a vector */
static int save_numbers(char const *file, int **vet, int *num)
{
FILE *fin;
int i = 0;
int n_max = 0;
char buff[10];
if ((fin = fopen(file, "r")) == 0)
return -1;
while (fgets(buff, sizeof(buff), fin) != 0)
{
if (i >= n_max)
{
int n_new = (2 * n_max) + 2;
void *v_new = (int *)realloc(*vet, n_new * sizeof(int));
if (v_new == 0)
return -1;
*vet = v_new;
n_max = n_new;
}
if (sscanf(buff, "%d", &(*vet)[i]) != 1)
break;
i++;
}
*num = i;
/* Optionally release surplus space - if there is enough to warrant doing so */
if (i + 8 < n_max)
{
void *v_new = realloc(*vet, i * sizeof(int));
if (v_new == 0)
return -1;
*vet = v_new;
}
return fclose(fin);
}
int main(void)
{
int i, num, *vet = NULL;
if (save_numbers("numbers.dat", &vet, &num))
{
perror("numbers.dat");
exit(1);
}
/* print test */
for (i = 0; i < num; i++)
printf ("%d ", vet[i]);
printf("n");
free(vet);
return 0;
}
讨论
我正在努力理解代码,但可能我还是太笨了。
出了什么问题?我引入了新的变量n_max来计算分配了多少行;该数字最初为零。当读取新行时,代码会检查数组中是否还有空槽(i>=n_max)。如果没有剩余空间,(2*n_max)+2计算(我通常对序列2、6、14、30使用该计算,或对序列4、12、28、60使用(2+n_max)*2——两者都确保经常进行重新分配以进行测试)会给出一个新的非零大小。然后,代码分配空间,在覆盖前一个指向已分配内存的指针之前检查分配是否成功,从而避免内存泄漏。如果一切正常,则分配新指针和新大小,并或多或少地像以前一样继续,但检查sscanf()
是否工作。
但为什么是
int n_new = (2 * n_max) + 2;
?为什么选择(2 * n_max) + 2;
?
因为2*0是0,所以它不会比分配0分配更多的空间。
[1]可能我不太清楚realloc()是如何工作的。为什么
void *v_new = (int *)realloc(*vet, n_new * sizeof(int));
中同时存在*v_new
和*vet
?
Buglet;它要么应该是int *v_new = (int *)realloc(*vet, n_new * sizeof(int));
,要么应该是void *v_new = realloc(*vet, n_new * sizeof(int));
,尽管所写的是有效的。至于为什么会有额外的变量,看看当n_new
为6时,如果内存分配会发生什么。变量CCD_ 19保存指向包含2个数字的原始数据的唯一指针。如果分配失败,但您已经编写了*vet = (int *)realloc(*vet, n_new * sizeof(int));
,那么您也失去了释放其他2个数字的机会——您不再有指向它们的指针。
一般来说,成语:
pointer = realloc(pointer, new_size);
是有风险的,因为它丢失了指向上一个分配的指针。这就是为什么上面的代码将新指针保存到不同的变量中。
还要注意,void *v_new
中的*
与*vet
中的*
不同。在v_new
的声明中,它表示该类型是一个指针。在赋值的RHS中,它是解引用运算符。
[2]为什么选择
*vet = v_new;
?
将新指针保存到v_new
中后,一旦验证,则可以安全地分配给
[3]为什么选择
if (i + 8 < n_max)
?以及它的内容?
如果有足够多的过度分配的内存值得担心(8个整数大约是64位机器上有意义的最小值),那么"可选释放剩余空间"代码会释放块末尾未使用的内存。该标准并没有说realloc()
在收缩时不会移动数据,尽管realloc()
等人在收缩时移动数据的实现非常罕见。在该代码中省略"realloc()
返回NULL"检查非常诱人。
如果i
在n_max
的8以内(这相当于8个整数,或者通常是32个字节),那么可能没有足够的可用空间值得释放。在64位系统上,最小分配通常为16个字节,即使分配连续的单个字符,返回的指针也通常相隔16个字节。因此,返回小于16个字节通常是完全否定的。返回32个字节更有可能是可用的。这是一个判断,但4或8是合理的数字,16不会出错——或者你可以完全忽略超额分配。(另一方面,如果您将分配从1GiB增加到2GiB,然后使用第二个GiB中的256个字节,则可能值得返回第二个GiB的剩余数据。)
[4]如果我理解的话,告诉我:我的版本2还可以,但你的代码更好,因为它不会为每个数字重新分配内存,但会分配更多的内存。在第一个周期,它分配
2*sizeof(int)
,在第二个周期,分配6*sizeof(int)
,在第三个周期分配14*sizeof(int)
,等等。然后,如果分配的内存太多,它用if (i + 8 < n_max)
释放它。正确的我明白了吗?
这几乎是正确的。当然,你所指的"周期"意味着代码不会在每次读取数字时进行分配。当代码读取第二个数字时,它不会分配任何内容;它第一次为2个数字分配了足够的空间。当它读取第三个数字时,分配变为6个数字,因此它读取第四个到第六个数字,而不需要进行另一个分配。因此,它只进行了2次分配,而不需要6次分配就可以读取第六个数字,这是一个相当大的节省。
[5]如何处理错误?例如,如果
if (v_new == 0)
为true(v_new为0),则函数返回-1,main执行perror("numbers.dat");
,但这不是文件错误。
有多种方法可以做到这一点。我在对chux的评论响应中提到,我通常会单独报告内存错误;事实上,我通常也会在函数中向下报告文件处理错误,而不是在main()
中使用向上报告。一个通常有用的约定是让检测错误的底层报告它,但将失败的信息传达回调用代码。除其他外,这意味着您可以区分打开文件的错误和关闭文件的错误,因为函数在检测到错误时知道要做什么,但调用函数只知道在处理文件时出现了某种问题。你可以进入或多或少精心设计的记录错误并向调用链报告错误的方案。
[6]编写
if (sscanf(buff, "%d", &(*vet)[i]) != 1)
与if (sscanf(buff, "%d", &(*vet)[i]) == EOF)
相同吗?但是while (fgets(buff, sizeof(buff), fin))
还没有完成文件控制的结束?
如果字符串中没有数据(它是一个空字符串),sscanf()
调用将返回EOF。如果有字符,但不能将其视为数字,它将返回0。如果有一系列字符组成一个数字(但有效数字后面可能紧跟着字母或标点符号),它将返回1。同样,请参阅注释,了解如何处理此问题的部分讨论,尽管该注释更多的是关于在一行中处理多个数字。
[7]我还没有弄清楚为什么
if (i + 8 < n_max)
。你已经告诉我了,但我不明白。为什么我不能做if (i < n_max)
?
您可以执行if (i < n_max)
。但是,如果您通过realloc()
返回4个字节(因为i + 1 == n_max
),系统很可能无法使用它做任何有用的事情,因此您通过调用没有取得任何成就。OTOH,尝试释放每个字节并没有太大的危害。我的猜测是,如果你分配了几百个或更多的数字,并且在你将一个值读入最后一个之前文件就结束了,你通常会释放几百字节(或更多)的空间,这可能会很有用。这是一个判断。我选择把事情复杂化;很抱歉我这么做了。
将(整个)文件读取到大小与文件大小相同的缓冲区中。无realloc
。
关闭你的文件,不要担心稍后在程序中泄露句柄。
扫描所述缓冲区以寻找换行符。数数。不要担心数字。
既然你知道有多少,就分配你的数字数组。无realloc
。在再次扫描缓冲区时写入该数组。
如果您不再需要缓冲区,请将其释放。
除非你谈论的是到其他进程的管道、套接字或4+GB文件,否则用低级语言缓冲人类语言数据是没有意义的。或者,如果你正在编写将在电梯控制器、微波炉、电动剃须刀或其他设备上运行的代码,但你没有任何文件,嗯?如果您的文件适合您的地址空间(现在大多数VAST文件都适合),那么缓冲是对内存占用的过早优化,这将花费您的时间,包括写入时间和运行时间。