我正在尝试解析一些只提取第n个字段的CSV日志文件(为了速度而忽略其他字段)。当我使用fread大于输入大小的缓冲区大小时,我的函数按预期工作。
问题是当我读取部分输入并尝试继续下次调用该函数时离开的位置。我相信问题在于我如何处理null终止符并设置我的全局变量,但我似乎无法弄明白。
任何有助于理解我做错事的帮助都非常感谢!
代码
#include <stdio.h>
#include <time.h>
int gcomc = 0;
int gpos = 0;
void test(char *str, int len)
{
const char *ptr = str;
char ch;
int i;
char so[10];
int comc = gcomc;
int pos = gpos;
for(i = 0; i < len; i++)
{
ch = ptr[i];
switch(ch)
{
case ';':
comc++;
break;
case '\0':
gcomc = comc;
gpos = pos;
break;
default:
if (comc == 3) {
ch = ptr[i];
so[pos++] = ch;
}
if (comc == 7) {
printf(" %s ", so);
comc = 0;
pos = 0;
gcomc = 0;
gpos = 0;
}
}
}
return;
}
int main(int argc, char* argv[])
{
FILE *fin=fopen("test.txt", "rb");
char buffer[100 + 1];
size_t bsz;
while((bsz = fread(buffer, sizeof *buffer, 100, fin)) > 0)
{
buffer[bsz] = '\0';
test(buffer, bsz);
}
return 1;
}
输入
A;B;C;D;E;F;G;H
I;J;K;L;M;N;O;P
Q;R;S;T;U;V;W;X
Y;Z;1;2;3;4;5;6
缓冲区大小为100(101)的输出
D L T 2
缓冲区大小为10(11)的输出
D P
Q X
Segmentation fault (core dumped)
编辑: 感谢您的评论和代码,我重写了我的(相当愚蠢的书面)代码 - 任何进一步的批评都是受欢迎的(建设性的或破坏性的,我从中学习):
#include <stdio.h>
#include <time.h>
void test(char *str, int len);
int gcomc, gpos = 0;
void test(char *str, int len)
{
const char *ptr = str;
char ch;
int i;
static char so[10];
for(i = 0; i < len; i++)
{
ch = ptr[i];
switch(ch)
{
case ';':
gcomc++;
break;
default:
if (gcomc == 3) {
ch = ptr[i];
so[gpos++] = ch;
}
if (gcomc == 7) {
so[gpos] = '\0'; /* ensure so is null terminated */
printf(" %s ", so);
gcomc = 0;
gpos = 0;
}
}
}
return;
}
extern int main()
{
FILE *fin=fopen("test.txt", "rb");
char buffer[10 + 1];
size_t bsz;
while((bsz = fread(buffer, sizeof *buffer, sizeof buffer, fin)) > 0)
{
test(buffer, bsz);
}
return 1;
}
答案 0 :(得分:1)
您的代码中至少有两个问题是能够以块的形式读取文件。
首先,so
数组是自动的:它没有理由将其值从一次调用保持到其他数组。您应该将其声明为全局(在测试函数之外)或静态。
接下来,只有在找到null时才将本地状态复制到全局状态。但是null位于len位置,你就在退出循环之前(for(i = 0; i < len; i++)
注意 <
)所以在下一次调用时你再次以0,0开始。你应该选择一种方法来指示缓冲区的结束,或者传递一个长度,写一个空标记,但混合两者都是容易出错的。当你使用fread
时,我的建议是坚持一个长度:
主要用途:
while((bsz = fread(buffer, sizeof *buffer, sizeof buffer, fin)) > 0)
{
test(buffer, bsz);
}
(这样,你只能写一次缓冲区的大小)
并且在测试中:
void test(char *str, int len)
{
const char *ptr = str;
char ch;
int i;
static char so[10];
int comc = gcomc;
int pos = gpos;
for(i = 0; i < len; i++)
{
ch = ptr[i];
switch(ch)
{
case ';':
comc++;
break;
default:
if (comc == 3) {
ch = ptr[i];
so[pos++] = ch;
}
if (comc == 7) {
so[pos] = '\0'; /* ensure so is null terminated */
printf(" %s ", so);
comc = 0;
pos = 0;
gcomc = 0;
gpos = 0;
}
}
}
gcomc = comc; /* store the state to globals */
gpos = pos;
return;
}
但正如你在评论中所说的那样混合局部和全局变量是容易出错的。看起来您在设计程序结构之前就开始编码,并确定实际需要全局化的内容。你没有,是吗? ; - )
答案 1 :(得分:0)
test()
内的解析器状态需要在多次调用中存活。你处理这个问题只是让计数器全局化。全局是不好的做法。您也错过了保存so
的状态(内容)。
将状态封装在结构中。
#include <stdlib.h>
#include <stdio.h>
#define SO_SIZE (10)
struct state
{
size_t comc;
size_t pos;
char so[SO_SIZE + 1]; /* Add 1 for the 0-terminator. */
}
并将其传递给解析器的每次调用(此处test()
)。
像这样调整解析器:
int test(struct state * pstate, const char *str, size_t len)
{
int result = 0; /* be optimistic. */
char ch;
size_t i;
for (i = 0; i <= len; i++)
{
ch = str[i];
switch (ch)
{
case ';':
pstate->comc++;
break;
default:
if (pstate->comc == 3)
{
ch = str[i];
if (SO_SIZE <= pstate->pos)
{
result = -1; /* overflow */
break;
}
pstate->so[pstate->pos++] = ch;
}
if (pstate->comc == 7)
{
printf(" %s ", pstate->so);
pstate->comc = 0;
pstate->pos = 0;
}
}
}
return result;
}
然后这样称呼:
#define BUFFER_SIZE (100)
int main(void)
{
FILE *fin = fopen("test.txt", "rb");
if (NULL == fin)
{
perror("fopen() failed");
return EXIT_FAILURE;
}
{
char buffer[BUFFER_SIZE + 1] = {0};
size_t bsz;
struct state state = {0};
int result;
while (0 < (bsz = fread(buffer, sizeof *buffer, sizeof buffer, fin))
&& (0 == result))
{
result = test(&state, buffer, bsz);
}
return result ?EXIT_FAILURE :EXIT_SUCCESS;
}
}