使用固定大小的动态字符串数组fgets()
使用fgets()
—strdup()
是这里唯一不完全可移植的部分:
char *data[10];
int i; // Needs to last longer than the loop
for (i = 0; i < 10; i++)
{
char buffer[4096];
if (fgets(buffer, sizeof(buffer), fp) == 0)
break;
buffer[strlen(buffer)-1] = '\0'; // Zap last character; normally newline
data[i] = strdup(buffer); // Error check allocation?
}
如果疯子使用它来读取包含超过 4095 个字符的单行数据的大型 JSON 文件,这可能会遇到问题。对于大多数目的,您不太可能遇到 4 KiB 的行,但这是您的判断。
使用固定大小的动态字符串数组getline()
使用 POSIX 2008 getline()
— 不同的可移植性问题:
char *data[10];
int i;
for (i = 0; i < 10; i++)
{
char *buffer = 0;
size_t buflen = 0;
ssize_t actlen;
if ((actlen = getline(&buffer, &buflen, fp)) < 0)
break;
buffer[actlen-1] = '\0'; // Zap last character; normally newline
data[i] = buffer;
}
这不会对行的长度施加任何上限,并getline()
分配所有空间。
请注意,我没有检查 zapped 字符是否是任一片段中的换行符。您可以(可以说应该)添加该检查。您可能有一个文件末尾没有换行符;它在技术上不是一个文本文件(它们总是以换行符结尾),但它在 Unix 系统上有效。
固定大小字符串的固定大小数组
保留预先分配的内存:
char data[10][26];
int i;
for (i = 0; i < 10; i++)
{
if (fscanf(fp, "%25s", data[i]) != 1)
break;
int c; // Gobble new line
while ((c = getc(fp)) != EOF && c != '\n')
;
}
请注意,这读取的是单词,而不是行。它停在空白处。要读取行,您将使用扫描集转换规范:
if (fscanf(fp, "%25[^\n]", data[i] != 1)
然后,您必须决定是否像以前一样吞噬该行的其余部分,或者是否插入一个微妙但至关重要的空格" %25[^\n]"
来吞噬空白(在开始转换之前吃掉空白)。
动态字符串的动态数组
char **data = 0;
size_t numstr = 0; /* Number of strings in use */
size_t maxstr = 0; /* Number of pointers allocated */
char *buffer = 0;
size_t buflen = 0;
ssize_t actlen;
while ((actlen = getline(&buffer, &buflen, fp)) > 0)
{
if (numstr >= maxstr)
{
assert(numstr == maxstr);
size_t newnum = maxstr * 2 + 2;
void *newspc = realloc(data, newnum * sizeof(char *));
if (newspc == 0)
{
/* memory allocation failed - data still valid */
break;
}
maxstr = newnum;
data = newspc;
}
buffer[actlen-1] = '\0'; // Zap last character; normally newline
data[numstr++] = buffer;
buffer = 0; // Reset so getline() allocates on next read
buflen = 0;
}
不是每个人都赞成使用realloc()
最初分配然后重新分配内存空间;malloc()
如果你愿意,你可以在循环之前做一个。确保您在第2 * maxstr + 2
一次分配时获得非零计数(实际上是 2),并且足够小以测试重新分配代码(一个好主意)。每次翻倍摊销分配的成本。在循环之后,您可以将data
数组重新分配以缩小到实际大小:
realloc(data, numstr * sizeof(char *));
你应该检查它没有失败,但它不应该这样做。这样做是否真的值得商榷。
警告
上面代码的一个变体现在已经用编译器和测试程序进行了正式测试。 SSCCE 如下所示。
请注意,分配的内存不会在上方(或下方)释放。始终确保您知道何时释放分配的内存。通常,这意味着您需要一个合适的功能来完成这项工作;在程序退出时让 O/S 释放内存通常是不可接受的。
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void dump_strings(FILE *fp, const char *tag, size_t num, char **data)
{
fprintf(fp, "%s:\n", tag);
for (size_t i = 0; i < num; i++)
fprintf(fp, " %2zu: [%s]\n", i, data[i]);
}
static void func1(FILE *fp)
{
char *data[10];
int i; // Needs to last longer than the loop
for (i = 0; i < 10; i++)
{
char buffer[4096];
if (fgets(buffer, sizeof(buffer), fp) == 0)
break;
buffer[strlen(buffer)-1] = '\0'; // Zap last character; normally newline
data[i] = strdup(buffer); // Error check allocation?
}
dump_strings(stdout, "func1", i, data);
/* Leak! */
}
static void func2(FILE *fp)
{
char *data[10];
int i;
for (i = 0; i < 10; i++)
{
char *buffer = 0;
size_t buflen = 0;
ssize_t actlen;
if ((actlen = getline(&buffer, &buflen, fp)) < 0)
break;
buffer[actlen-1] = '\0'; // Zap last character; normally newline
data[i] = buffer;
}
dump_strings(stdout, "func2", i, data);
/* Leak! */
}
static void func3(FILE *fp)
{
char data[10][26];
size_t i;
for (i = 0; i < 10; i++)
{
if (fscanf(fp, "%25[^\n]", data[i]) != 1)
break;
int c;
while ((c = getc(fp)) != EOF && c != '\n')
;
}
printf("%s:\n", "func3");
for (size_t j = 0; j < i; j++)
printf("%2zu: [%s]\n", j, data[j]);
}
static void func4(FILE *fp)
{
char **data = 0;
size_t numstr = 0; /* Number of strings in use */
size_t maxstr = 0; /* Number of pointers allocated */
char *buffer = 0;
size_t buflen = 0;
ssize_t actlen;
while ((actlen = getline(&buffer, &buflen, fp)) > 0)
{
if (numstr >= maxstr)
{
assert(numstr == maxstr);
size_t newnum = maxstr * 2 + 2;
void *newspc = realloc(data, newnum * sizeof(char *));
if (newspc == 0)
{
/* memory allocation failed - data still valid */
break;
}
maxstr = newnum;
data = newspc;
}
buffer[actlen-1] = '\0'; // Zap last character; normally newline
data[numstr++] = buffer;
buffer = 0; // Reset so getline() allocates on next read
buflen = 0;
}
dump_strings(stdout, "func4", numstr, data);
/* Leak! */
}
int main(void)
{
func1(stdin);
func2(stdin);
func3(stdin);
func4(stdin);
return(0);
}
除了像筛子一样漏水外,valgrind
说这还可以。它主要是在自己的源代码上进行测试的。