至少有两种方法可以在读取行时构建表格。一个使用realloc() 的属性,如果它的第一个参数是空指针,它将表现得像malloc() 并分配请求的空间(因此代码可以自启动,单独使用realloc())。该代码可能如下所示:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { LEN = 1024*8 };
static void error(const char *fmt, ...);
static char *xstrdup(const char *str);
int main(void)
{
char line[LEN];
char **tab = NULL;
int tabsize = 0;
int lineno = 0;
while (fgets(line, sizeof(line), stdin) != 0)
{
if (lineno >= tabsize)
{
size_t newsize = (tabsize + 2) * 2;
char **newtab = realloc(tab, newsize * sizeof(*newtab));
if (newtab == 0)
error("Failed to allocate %zu bytes of memory\n", newsize * sizeof(*newtab));
tab = newtab;
tabsize = newsize;
}
tab[lineno++] = xstrdup(line);
}
/* Process the lines */
for (int i = 0; i < lineno; i++)
printf("%d: %s", i+1, tab[i]);
/* Release the lines */
for (int i = 0; i < lineno; i++)
free(tab[i]);
free(tab);
return(0);
}
static void error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}
static char *xstrdup(const char *str)
{
size_t len = strlen(str) + 1;
char *copy = malloc(len);
if (copy == 0)
error("Failed to allocate %zu bytes of memory\n", len);
memmove(copy, str, len);
return(copy);
}
当表为空时,替代方案显式使用malloc(),并且最简单的编码为:
int main(void)
{
char line[LEN];
int tabsize = 4;
int lineno = 0;
char **tab = malloc(tabsize * sizeof(*tab));
if (tab == 0)
error("Failed to allocate %zu bytes of memory\n", tabsize * sizeof(*tab));
...
其他一切都可以保持不变。
请注意,让函数 xmalloc() 和 xrealloc() 保证永远不会返回空指针会很方便,因为它们会报告错误:
static void *xmalloc(size_t nbytes)
{
void *space = malloc(nbytes);
if (space == 0)
error("Failed to allocate %zu bytes of memory\n", nbytes);
return(space);
}
static void *xrealloc(void *buffer, size_t nbytes)
{
void *space = realloc(buffer, nbytes);
if (space == 0)
error("Failed to reallocate %zu bytes of memory\n", nbytes);
return(space);
}
从长远来看(大型程序),这会减少您编写“内存不足”错误消息的总次数。另一方面,如果您必须从内存分配失败中恢复(保存用户的工作等),那么这不是一个合适的策略。
上面的代码创建了一个参差不齐的数组; tab 中的不同条目具有不同的长度。如果你想要同质长度(如在原始代码中),那么你必须替换或修改xstrdup()函数来分配最大长度。
您可能已经注意到xstrdup() 中的代码使用memmove() 而不是strcpy()。那是因为strlen() 已经测量了字符串的长度,所以不需要复制代码来测试每个字节是否需要复制。我使用了memmove(),因为它永远不会出错,即使字符串重叠,即使在这种情况下很明显字符串永远不会重叠,所以memcpy() - 如果字符串重叠,则不能保证正常工作 -可以使用,因为字符串不能重叠。
分配(oldsize + 2) * 2 新条目的策略意味着内存重新分配代码在测试过程中得到足够频繁的执行,而不会过度影响生产中的性能。请参阅 Kernighan 和 Pike 的 The Practice of Programming,了解为什么这是一个好主意。
我几乎总是使用一组类似于error() 函数的函数,因为它极大地简化了错误报告。我通常使用的函数是记录和报告程序名称(来自argv[0])的包的一部分,并且具有相当广泛的替代行为。