Reputation:
I am having difficulty with using the fread() function to read values from a file for a very specific and unlikely use case. This sounds really stupid, but an assignment I am working on requires me to do a partial read of items in a file.
My file name is "test_file.txt", and it's written in the following format, with one integer per line:
23
4
10
My code snippet, which is meant to process the above three bytes at a time, looks like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main () {
int value = 0;
char buffer[3];
FILE* channel_file = fopen("test_file.txt", "rb");
while(fread(&buffer, sizeof(char), sizeof(buffer), channel_file) > 0) {
sscanf(buffer, "%d", &value);
printf("%d \n", value);
memset(buffer, 0, sizeof(buffer));
}
}
Since I am reading the file 3 bytes at a time, I was expecting it to look like this:
23\n
4\n1
0\n
which does seem to be the case (since \n counts as a byte). Printed, I wanted to see
23
41
0
However, when I try to print these values using the code above, I only get this:
23
4
I have tried a variety of different parameters in the fread() function itself, but none of them give the expected output. I was hoping someone more familiar with C and string parsing could help.
** EDIT: Posted a solution below. The solution is prone to undefined behavior since buffer
and str
are not null terminated. A mechanism for adding null termination to the buffer
and str
char array would improve the solution here.
Upvotes: 2
Views: 248
Reputation: 93556
You seem to have made a very simple thing rather complicated. What you appear to require from your description is to:
read the file three bytes at a time and to print only the digit characters from those three bytes on a new line.
A simple and clear statement of requirements leads to a simpler solution. I wonder what the actual stated requirements were in the original assignment?
Given the stated precondition that the file contains only digits and newlines, then there is no need even to convert anything to an integer. Neither is there any need for complex string manipulation or string termination.
#include <stdio.h>
int main()
{
FILE* channel_file = fopen( "test_file.txt", "r");
if( channel_file != NULL )
{
size_t count = 0 ;
char buffer[3] ;
while( 0 < (count = fread(&buffer, 1u, sizeof(buffer), channel_file)) )
{
for( size_t c = 0; c < count; c++ )
{
if( buffer[c] != '\n' )
{
putchar( buffer[c] ) ;
}
}
putchar( '\n' ) ;
}
}
return 0 ;
}
Noting also that sizeof(char)
is 1 by definition. It is not wrong, but not necessary to ask the size of a char
.
A "safer" method that does not rely on the file containing only digits and newline is to #include <ctype.h>
then then change the digit output condition to:
if( isdigit( buffer[c] ) )
{
putchar( buffer[c] ) ;
}
If on the other hand the assignment requires you to determine an integer value, then the while
loop body can be changed to:
unsigned value = 0 ;
for( int c = 0; c < count; c++ )
{
if( isdigit( buffer[c] ) )
{
value *= 10 ;
value += buffer[c] - '0' ;
}
}
printf( "%u\n", value ) ;
avoiding any building of a string in order to perform the conversion.
Upvotes: 1
Reputation: 154169
On review, the approach below, given OP's file input of "23\n4\n10\n"
, prints:
23
4
10
This is not OP's goal of:
23
41
0
Seems I mis-understood OP's goal. Making answer wiki for reference.
It appears OP is obliged to use fread()
for input. Sigh.
Consider using fread()
to read in up to so many characters. Maybe up to 3 in this case.
Open the file in text mode to handle systems that use "\r\n"
as a single '\n'
. fopen("test_file.txt", "rb")
--> fopen("test_file.txt", "r")
.
Make the buffer one larger so we can append a null character and process buffer
as a string. sscanf(buffer...
expects a string.
No need for &buffer
. buffer
itself will convert to the type of &buffer[0]
.
Use " %n"
to store how many characters were consumed converting the string into an int
. Use that to adjust how many characters to read in the next fread()
.
No need for a space before printing a '\n'
.
Good practice: check return value from sscanf()
.
// Untested illustrative code.
#define BUFFER_MAX_LEN 3
char buffer[BUFFER_MAX_LEN + 1];
size_t buffer_len = 0; // Count of unprocessed characters in the buffer.
size_t buffer_read;
do {
buffer_read = fread(&buffer[buffer_len], sizeof buffer[0],
BUFFER_MAX_LEN - buffer_len, channel_file);
buffer_len += buffer_read;
buffer[buffer_len] = '\0'; // Make buffer[] a string
int n;
int conversion_count = sscanf(buffer, "%d %n", &value, &n);
if (conversion_count != 1) {
printf("Nothing to convert from <%s>. Quitting\n", buffer);
break;
}
printf("%d\n", value);
buffer_len -= n;
// Move unused trailing characters to the front.
memmove(buffer, buffer + n, buffer_len);
} while (buffer_read > 0);
A more advanced approach would use a much larger buffer and look for multiple int
in the string. We could then reduce the wasteful memmove()
.
It is more robust to use strtol()
than sscanf(..."%d ...
Leave those for a later day.
Upvotes: 3