8

The declaration of the fgets function look like this:

char *fgets(char *str, int n, FILE *stream);

This means that the second argument is expected to be an int.

Which is the proper way to avoid this casting in the following program?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    const char *buffer = "Michi";
    size_t len = strlen(buffer) + 1;
    char arr[len];

    printf("Type your Input:> ");
    if (fgets(arr, (int)len, stdin) == NULL) {
        printf("Error, fgets\n");
        exit(1);
    } else {
        printf("Arr = %s\n", arr);
    }
}

Here I used (int)len which looks fine, but what happens if buffer stores a very long string?

Lets say:

const char *buffer = "Very long long ..."; /* where length is beyond the range of the `int` type */

I already declared length of the type size_t which is fine here, but if I'll pass it to fgets is not OK because of:

conversion to ‘int’ from ‘size_t {aka long unsigned int}’ may alter its value

and if I cast it to int won't fit because the size of the int is smaller than the size of length and some information will be lost.

Maybe I'm missing something here...

Any way how should I avoid this situation?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Michi
  • 5,175
  • 7
  • 33
  • 58
  • 1
    Just saying, in that case, are you sure `char arr[len];` will work properly? It's on stack with gcc, anyways.. – Sourav Ghosh Jul 18 '16 at 10:45
  • 1
    @SouravGhosh I could use malloc here , but the question is not about that – Michi Jul 18 '16 at 10:46
  • Maybe http://stackoverflow.com/questions/8317295/convert-unsigned-int-to-signed-int-c is helpful for you. – Sergio Jul 18 '16 at 10:47
  • @Serhio Does that include `long unsigned int` too? – Michi Jul 18 '16 at 10:48
  • @Michi I think so. ... casting between signed/unsigned for values out of range is implementation-defined ... The only criteria is being out of range of destination type, regardless of `long` or `short` modifiers. – Sergio Jul 18 '16 at 10:55
  • @Serhio could you provide an Example where I can check it? – Michi Jul 18 '16 at 10:56
  • In practice I've often come across: (1)`*buffer` being allocated to some reasonable size and file (stream) read in chunks of buffer size and transferred elsewhere, whereas casting `size_t` to `int` wouldn't compromise a temporary stack variable. (2) length of file known, "`*buffer`" memory is dynamically allocated upon size of file with entire content of file transferred to "buffer". – user3078414 Jul 18 '16 at 10:57

4 Answers4

5
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);

With fgets(), the input buffer s[INT_MAX] and beyond cannot be used.


OP's size_t len code could avoid the int cast of len by converting to a string and converting back to an int, that is just wasteful. A cast is the right thing to do.

Rather than littering code with a (int), reduce/control its usage and wrap the cast in a limiting helper function.

int fgets_len(size_t len) {
  return (len < INT_MAX) ? (int) len : INT_MAX;
}


size_t len = something_big;
char *arr = malloc(len);

...   
if ( fgets(arr, fgets_len(len), stdin) == NULL){
    printf("Error, Fgets\n");
    exit(1);
}else{
    printf("Arr = '%s'\n", arr);
}

If code truly needs to read long lines, consider ssize_t getline(char **lineptr, size_t *n, FILE *stream); as defined here. Note this is a non-standard C library function, yet its source code is readily available.


Concerning pedantic use of fgets(), there are at least 2 reasons for fgets() to return NULL: End-of-file and input error. Now consider the corner case using OP's code

size_t len = strlen("") + 1;
char arr[len];
if ( fgets(arr, (int)len, stdin) == NULL){

and pathological cases like

if ( fgets(arr, 0, stdin) == NULL){
if ( fgets(arr, -1, stdin) == NULL){

Both are discussed at Is fgets() returning NULL with a short bufffer compliant?

Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • *With `fgets()`, the input buffer `s[INT_MAX]` and beyond cannot be used.*: more precisely, a buffer of any size can be used, but if it is longer than `INT_MAX` its size cannot be used directly. Note that `s[INT_MAX]` is not a problem. – chqrlie Jan 31 '17 at 07:42
2

If you want to avoid the trouble of integer overflow, you can do value checking and act accordingly. Note that, since strlen() returns value of type size_t you have 2 options. Either declare a variable of type int and assign it the return value of strlen(), which will do an implicit conversion from type size_t to int, or cast as you did in you function invocation.

Here is a possible solution:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

int main(void)
{
    const char *buffer = "Michi";
    //size_t len = strlen(buffer) + 1;
    size_t len = 9999999999;

    if (len > INT_MAX) {
        fprintf(stderr, "Error, length exceeded desired value.Aborting...\n");
        return 1;
    }

    char arr[len];

    printf("Type your Input:> ");
    if (fgets(arr, (int)len, stdin) == NULL) {
        printf("Error, Fgets\n");
        exit(1);
    }
    else {
        printf("Arr = %s\n", arr);
    }
    return 0;
}
  • I already know that `len > INT_MAX` so here `if (len > INT_MAX)` my program will stop working which is wrong. – Michi Jul 18 '16 at 11:25
  • @Michi - If you call `fgets` with `n <= 0`, it will return `NULL` without even waiting for input so your program will terminate anyway. So the best thing to do, is to check `len` before going. – Support Ukraine Jul 18 '16 at 11:32
  • @Michi - Also the `size_t` to `int` could end up with a positive but meaningless value. Example `0x100000003` could become `3` which would make your program behave strange. So check that `len` doesn't exceed `INT_MAX` before doing the call. – Support Ukraine Jul 18 '16 at 11:48
1

I wouldn't bother to link the dimension of the argument to fgets() with the dimension of buffer at all.

Instead, I'd ensure that the buffer being used for reading has a length that can be represented using an int (say, does not exceed INT_MAX). If you want to be truly portable, ensure the buffer length does not exceed 32767 (the standard specifies that the minimum allowed value of INT_MAX is 32767).

Then make use of the fact that fgets() will read a line in parts, if the line length exceeds the buffer length.

For example, assuming that len exceeds the length of any line being read from stdin;

char arr[len] = {0};   
char read_buffer[10];    /*   I'm reasonably confident that 10 < 32767 */

while (fgets(read_buffer, 10, stdin) != NULL)
{
      size_t read_length = strlen(read_buffer);
      if (read_length > 0)
      {
           if (read_buffer[read_length-1] != `\n`)
           {
               strcat(arr, read_buffer);
           }
           else
           {
               strncat(arr, read_buffer, read_length-1);

               printf("Arr = %s\n", arr);

               arr[0] = '\0';    /*  clear arr so next line may be read */

               /*  break here if want to stop reading after the first line */

           }
      }

Note that, if end of file is not immediately preceded by a '\n', then the above will discard the text after the last '\n'.

In the above, replacing fgets(read_buffer, 10, stdin) with fgets(read_buffer, sizeof read_buffer, stdin) is safe, since a size_t with value less than or equal to INT_MAX can always safely be converted to int. So, if you want to shut up a compiler from issuing warnings, you can safely cast i.e. fgets(read_buffer, (int)(sizeof read_buffer), stdin)

Peter
  • 35,646
  • 4
  • 32
  • 74
0

Draft says that

The result of converting an integer to a shorter signed integer, or the result of converting an unsigned integer to a signed integer of equal length, if the value cannot be represented

is implementation-defined. As result if you want safely convert value of unsigned type to signed - you must ensure that all possible source values can be represented with target, signed type.

Sergio
  • 8,099
  • 2
  • 26
  • 52