user22219231
user22219231

Reputation:

How would I use fread() for a partial read, and get my expected output here?

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

Answers (2)

Clifford
Clifford

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

chux
chux

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

Related Questions