Thomas Dickey 的回答(由 alk 编辑)是正确的。
为了理智,这里有一个示例,说明如何使用 getline() 逐行正确读取文件:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Read the named file line by line.
* Returns 0 if successful,
* nonzero (== errno) error code otherwise.
*/
int line_by_line(const char *const filename)
{
char *line = NULL;
size_t size = 0;
ssize_t len;
long linenum = 0L;
FILE *in;
/* Invalid filename? */
if (filename == NULL || *filename == '\0')
return errno = EINVAL;
/* Open file. If it fails, fopen() will set errno. */
in = fopen(filename, "rt");
if (in == NULL)
return errno;
/* Read loop. */
while (1) {
len = getline(&line, &size, in);
if (len <= (ssize_t)0)
break;
linenum++;
/* Note: if (strlen(line) < (size_t)len),
* the line contains embedded NUL (zero) bytes.
*/
/*
* Do something with the line ...
*/
printf("%ld: %s", linenum, line);
}
/* Discard possibly used line buffer. Note: free(NULL) is safe. */
free(line);
/* I/O error? */
if (!feof(in) || ferror(in)) {
fclose(in);
return errno = EIO;
}
if (fclose(in))
return errno = EIO;
/* All done successfully. */
return 0;
}
但是,如果文件看起来不像实际上是面向行的数据;文件中只有一条有趣的记录。谁知道呢,它也可能被分成多行。
这是我在这个案例中使用的:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
size_t read_file(const char *const filename, char **const dataptr, size_t *const sizeptr)
{
FILE *in;
char *data;
size_t size, more;
size_t have = 0;
/* Invalid parameters? */
if (filename == NULL || *filename == '\0' || dataptr == NULL || sizeptr == NULL) {
errno = EINVAL;
return (size_t)0;
}
/* If *dataptr is NULL, *sizeptr is assumed to be 0. */
data = *dataptr;
if (data != NULL)
*sizeptr = 0;
size = *sizeptr;
/* Open file, read-only, in binary mode. If it fails, fopen() sets errno. */
in = fopen(filename, "rb");
if (in == NULL)
return (size_t)0;
while (1) {
/* Need to grow the data buffer?
* (The +1 accounts for the end-of-buffer NUL byte). */
if (have + (size_t)1 >= size) {
/* Start with a 8k buffer.
* Double it in size for up to 1M,
* then round to next full megabyte. */
if (size < 8190)
size = 8192;
else
if (size < 1048576)
size *= 2;
else
size = (size | 524287) + 524289;
/* Size too large (wrapped)? */
if (size <= *sizeptr) {
free(data);
fclose(in);
*dataptr = NULL;
*sizeptr = 0;
errno = ENOMEM;
return (size_t)0;
}
/* Reallocate. */
data = realloc(data, size);
if (data == NULL) {
free(data);
fclose(in);
*dataptr = NULL;
*sizeptr = 0;
errno = ENOMEM;
return (size_t)0;
}
*dataptr = data;
*sizeptr = size;
}
/* Read more data, but remember to leave room for the
* terminating NUL byte. */
more = fread(data + have, 1, size - have - 1, in);
if (more > (size_t)0)
have += more;
else
break;
}
/* I/O error? */
if (!feof(in) || ferror(in)) {
free(data);
fclose(in);
*dataptr = NULL;
*sizeptr = 0;
errno = EIO;
return (size_t)0;
}
if (fclose(in)) {
free(data);
fclose(in);
*dataptr = NULL;
*sizeptr = 0;
errno = EIO;
return (size_t)0;
}
/* Append terminating NUL byte; we reserved space for it. */
data[have] = '\0';
/* Technically we shouldn't need to set errno. */
errno = 0;
return have;
}
struct state {
double tensionCoeff;
double speedCoeff;
double depthCoeff;
double tension;
double speed;
double depth;
double tensionRaw;
double speedRaw;
double depthRaw;
long connectionOk;
};
#define STATE_INITIALIZER { 0.0,0.0,0.0, 0.0,0.0,0.0, 0.0,0.0,0.0, 0L }
static char *state_buffer_data = NULL;
static size_t state_buffer_size = 0;
static void free_state_buffer(void)
{
free(state_buffer_data);
state_buffer_data = NULL;
state_buffer_size = 0;
}
#define UPDATED_TRIPLET (1U << 0)
#define UPDATED_CONN_OK (1U << 1)
unsigned int update_state(const char *const filename, struct state *const current)
{
unsigned int retval = 0U;
char *data;
size_t len;
if (current == NULL) {
errno = EINVAL;
return 0U;
}
len = read_file(filename, &state_buffer_data, &state_buffer_size);
if (!len) {
if (errno == 0)
errno = ENODATA;
return 0U;
}
data = strstr(state_buffer_data, "\"failedRequests\":");
if (data != NULL) {
long conn_ok;
if (sscanf(data+17, " %ld", &conn_ok) == 1) {
current->connectionOk = conn_ok;
retval |= UPDATED_CONN_OK;
}
}
data = strstr(state_buffer_data, "\"data\":[");
if (data != NULL) {
double value[6];
if (sscanf(data+8, " %lf , %lf , %lf , %lf , %lf , %lf ",
&(value[0]), &(value[1]), &(value[2]),
&(value[3]), &(value[4]), &(value[5])) == 6) {
current->tensionRaw = value[1];
current->depthRaw = value[4];
current->speedRaw = value[5];
current->tension = current->tensionCoeff * current->tensionRaw;
current->depth = current->depthCoeff * current->depthRaw;
current->speed = current->speedCoeff * current->speedRaw;
retval |= UPDATED_TRIPLET;
}
}
/* All done. If the buffer is twice as large, or at least two megabytes larger
than the amount of data we read, discard the buffer. */
if ((state_buffer_size > len + len && len > 4095) ||
state_buffer_size > len + 2048576)
free_state_buffer();
errno = 0;
return retval;
}
即使数据分布在多行上,这也能正常工作。我可能会使用自定义函数来查找数据起点(strstr() 太死板,甚至这里或那里的额外空间都会把它扔掉)。
缓冲区在update_state() 调用之间重复使用,除非新数据远小于缓冲区大小,在这种情况下,缓冲区将被丢弃(并在下一次调用期间根据需要重新分配),以保持较低的内存开销。 (但是,请注意,大多数 C 库实现不会将内存返回给操作系统,除非分配最初足够大以使用内存映射而不是 sbrk()。)
update_state() 总是将 errno 设置为零,如果没有发生错误。它的返回值是UPDATED_ 值的位掩码;如果找到 failedRequests 记录,(retval & UPDATED_CONN_OK) 为真,如果在参数结构中定位并更新了前六个数据字段中的三个数字字段,则(retval & UPDATED_TRIPLET) 为真。