I need to read a line up to 128 bytes in chunks of 16 bytes and I was recommended to use fgets but I have 2 mayor problems with it.
First, it doesn't stop reading when it detects the \n
That is simply incorrect. This has been mentioned by me [and others] in the prior question.
Once again, fgets
will stop when it sees \n
.
The code you have above that uses fgets
has UB (undefined behavior). It allocates a buffer of length 16. But, it passes 16 + 1
to fgets
. This means fgets
can read pass the end of the buffer causing UB (i.e. a bug).
You want the same length in both cases. And, if you want a 16 byte chunk of data, the length you want to pass is [probably] 17.
Referring to the prior question as well, which has an fgets
solution from me: How can I read from stdin until new line in groups of 16 bytes? c
You seem to want to use a solution that loops on read
in 16 byte chunks that stops on \n
. In other words, your teacher wants you to implement your own fgets
function.
The crux of the problem is that you need a struct
that remembers state across calls. It has to have a "hold" buffer that read
puts data into, maintaining an offset and length for the buffer. When a newline is detected, the data has to be copied out. The length is adjusted and the remaining chars [read in by read
but not passed to caller] must be moved to the front of the buffer.
Here is a test program that does that. It is a bit crude:
- It ignores the
mode
argument.
- It [probably] doesn't handle the final line correctly if it does not end with a newline.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef DEBUG
#define dbgprt(_fmt...) \
printf(_fmt)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
char buf[128 + 1];
typedef struct {
int fd;
int eof;
size_t len;
char buf[4096];
} XFIL;
char *
fix(const char *str,int len)
{
static char buf[4096];
char *bp = buf;
if (len < 0)
len = strlen(str);
for (int idx = 0; idx < len; ++idx) {
int chr = str[idx];
if (chr == 0)
break;
if ((chr >= 0x20) && (chr <= 0x7E))
*bp++ = chr;
else
bp += sprintf(bp,"{%2.2X}",chr & 0xFF);
}
*bp = 0;
return buf;
}
XFIL *
xopen(const char *file,const char *mode)
{
XFIL *xf = calloc(1,sizeof(*xf));
xf->fd = open(file,O_RDONLY);
return xf;
}
char *
xfgets(char *buf,size_t maxlen,XFIL *xf)
{
size_t off = xf->len;
ssize_t rdlen;
size_t curlen;
char *bp;
char *nl;
char *ret = NULL;
dbgprt("xfgets: ENTER maxlen=%zu eof=%d\n",maxlen,xf->eof);
while (1) {
dbgprt("xfgets: LOOP off=%zu eof=%d\n",off,xf->eof);
// find newline if we have one
if (xf->len > 0)
nl = memchr(xf->buf,'\n',xf->len);
else
nl = NULL;
// found a newline
if (nl != NULL) {
// get amount of data in buffer up to and including the newline
curlen = (nl - xf->buf) + 1;
dbgprt("xfgets: NLMATCH curlen=%zu\n",curlen);
// copy this out to caller's buffer (and add EOS)
memcpy(buf,xf->buf,curlen);
buf[curlen] = 0;
dbgprt("xfgets: RET buf='%s'\n",fix(buf,-1));
// set up return pointer
ret = buf;
// get amount of data remaining in our buffer
curlen = xf->len - curlen;
dbgprt("xfgets: REMLEN curlen=%zu\n",curlen);
// slide this to the front of our buffer
memmove(xf->buf,nl + 1,curlen);
dbgprt("xfgets: HOLD bf='%s'\n",fix(xf->buf,curlen));
xf->len = curlen;
break;
}
if (xf->eof)
break;
// read next chunk of file
bp = xf->buf + off;
rdlen = read(xf->fd,bp,16);
dbgprt("xfgets: READ rdlen=%zd\n",rdlen);
if (rdlen < 0) {
ret = NULL;
xf->eof = 1;
break;
}
dbgprt("xfgets: DATA bp='%s'\n",fix(bp,rdlen));
if (rdlen == 0)
xf->eof = 1;
// advance number of bytes in the hold buffer
xf->len += rdlen;
// advance offset into buffer
off += rdlen;
}
dbgprt("xfgets: EXIT ret=%p\n",ret);
return ret;
}
int
main(void)
{
const char *file = "inp.txt";
FILE *fin = fopen(file,"r");
XFIL *xf = xopen(file,"r");
char buf[2][1000];
char *ret[2];
while (1) {
ret[0] = fgets(buf[0],1000,fin);
ret[1] = xfgets(buf[1],1000,xf);
printf("main: RET %p/%p\n",ret[0],ret[1]);
if ((ret[0] != NULL) != (ret[1] != NULL))
break;
if (ret[0] == NULL)
break;
printf("BUF0: %s\n",fix(buf[0],-1));
printf("BUF1: %s\n",fix(buf[1],-1));
if (strcmp(buf[0],buf[1]) != 0) {
printf("error\n");
exit(1);
}
}
return 0;
}
Here is the test input:
abc
def
ghijklmnopqrstuvwxyz
qrm
Here is the program output [with -DDEBUG
]. I've indented it manually a bit to show the call sequence:
xfgets: ENTER maxlen=1000 eof=0
xfgets: LOOP off=0 eof=0
xfgets: READ rdlen=16
xfgets: DATA bp='abc{0A}def{0A}ghijklmn'
xfgets: LOOP off=16 eof=0
xfgets: NLMATCH curlen=4
xfgets: RET buf='abc{0A}'
xfgets: REMLEN curlen=12
xfgets: HOLD bf='def{0A}ghijklmn'
xfgets: EXIT ret=0x7fff5d044d48
main: RET 0x7fff5d044960/0x7fff5d044d48
BUF0: abc{0A}
BUF1: abc{0A}
xfgets: ENTER maxlen=1000 eof=0
xfgets: LOOP off=12 eof=0
xfgets: NLMATCH curlen=4
xfgets: RET buf='def{0A}'
xfgets: REMLEN curlen=8
xfgets: HOLD bf='ghijklmn'
xfgets: EXIT ret=0x7fff5d044d48
main: RET 0x7fff5d044960/0x7fff5d044d48
BUF0: def{0A}
BUF1: def{0A}
xfgets: ENTER maxlen=1000 eof=0
xfgets: LOOP off=8 eof=0
xfgets: READ rdlen=16
xfgets: DATA bp='opqrstuvwxyz{0A}qrm'
xfgets: LOOP off=24 eof=0
xfgets: NLMATCH curlen=21
xfgets: RET buf='ghijklmnopqrstuvwxyz{0A}'
xfgets: REMLEN curlen=3
xfgets: HOLD bf='qrm'
xfgets: EXIT ret=0x7fff5d044d48
main: RET 0x7fff5d044960/0x7fff5d044d48
BUF0: ghijklmnopqrstuvwxyz{0A}
BUF1: ghijklmnopqrstuvwxyz{0A}
xfgets: ENTER maxlen=1000 eof=0
xfgets: LOOP off=3 eof=0
xfgets: READ rdlen=1
xfgets: DATA bp='{0A}'
xfgets: LOOP off=4 eof=0
xfgets: NLMATCH curlen=4
xfgets: RET buf='qrm{0A}'
xfgets: REMLEN curlen=0
xfgets: HOLD bf=''
xfgets: EXIT ret=0x7fff5d044d48
main: RET 0x7fff5d044960/0x7fff5d044d48
BUF0: qrm{0A}
BUF1: qrm{0A}
xfgets: ENTER maxlen=1000 eof=0
xfgets: LOOP off=0 eof=0
xfgets: READ rdlen=0
xfgets: DATA bp=''
xfgets: LOOP off=0 eof=1
xfgets: EXIT ret=(nil)
main: RET (nil)/(nil)