1
\$\begingroup\$

Syntax: char *get_input_from_stdin_and_discard_extra_characters(char *str, long size);

Function get_input_from_stdin_and_discard_extra_characters() reads at most 'size - 1' characters into 'str' from stdin and then appends the null character ('\0').

The detailed description of this function is in the header file - "get_input_from_stdin.h".

Below is the code:


get_input_from_stdin.c


#include "get_input_from_stdin.h"

#include <stdio.h>

char *get_input_from_stdin_and_discard_extra_characters(char *str, long size)
{

    int c = 0;
    long i = 0;

    // If 'size' is 0 then this function will discard all input and return NULL.
    // No need to check 'str' if 'size' is 0.
    if (size == 0) {
        // discard all input
        while ((c = getchar()) && (c != '\n') && (c != EOF));
        return NULL;
    }

    if (!str)
        return str;

    if (size < 0)
        return NULL;

    for (i = 0; i < (size - 1); i = i + 1) {

        c = getchar();

        if ((c == '\n') || (c == EOF)) {
            str[i] = 0;
            return str;
        }

        str[i] = (char)(c);

    } // end of for loop

    str[i] = 0;

    // discard rest of input
    while ((c = getchar()) && (c != '\n') && (c != EOF));

    return str;

} // end of get_input_from_stdin_and_discard_extra_characters


get_input_from_stdin.h


#ifndef GET_INPUT_FROM_STDIN_H
#define GET_INPUT_FROM_STDIN_H

/*
 * get_input_from_stdin_and_discard_extra_characters(char *str, long size):
 *
 * Function get_input_from_stdin_and_discard_extra_characters() reads at most
 * 'size - 1' characters into 'str' from stdin and then appends the null
 * character ('\0'). If 'size' is 0 then this function will discard all input
 * and return NULL. So, to discard all input, this function can be called with
 * 'str' having value NULL and 'size' having value 0.
 * In all cases, reading input stops after encountering a newline ('\n') or EOF
 * even if 'size - 1' characters have not been read. If a newline ('\n') or EOF
 * is read then it is replaced by null character ('\0'). If there are extra
 * characters in input, they are read and discarded.
 * In all cases, 'str' or NULL is returned.
 */
char *get_input_from_stdin_and_discard_extra_characters(char *str, long size);

#endif


test_get_input_from_stdin.c


#include "get_input_from_stdin.h"

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

#define ARRAY_SIZE 11

int main(void)
{

    char str[ARRAY_SIZE] = {0};

    system("clear");

    printf("\n");
    if (get_input_from_stdin_and_discard_extra_characters(NULL, ARRAY_SIZE) != NULL) {
        printf("Test case having 'str' value as NULL failed.\n");
    } else {
        printf("Test case having 'str' value as NULL passed.\n");
    }
 
    printf("\nPlease input a string: ");
    if (get_input_from_stdin_and_discard_extra_characters(NULL, 0) != NULL) {
        printf("Test case having 'size' value as 0 failed.\n");
    } else {
        printf("Test case having 'size' value as 0 passed.\n");
    }

    strcpy(str, "#?-^");
    printf("\nPlease input a string: ");
    get_input_from_stdin_and_discard_extra_characters(NULL, 0);
    if (strcmp(str, "#?-^") != 0) {
        printf("Test case having 'size' value as 0 and a valid 'str' pointer failed.\n");
    } else {
        printf("Test case having 'size' value as 0 and a valid 'str' pointer passed.\n");
    }

    printf("\n\nPlease press ENTER to continue..");
    // now clear the stdin input buffer
    get_input_from_stdin_and_discard_extra_characters(NULL, 0);

    while (1) {

        system("clear");

        printf("\nPlease input a string (only first %d characters will be read): ", ARRAY_SIZE - 1);
        get_input_from_stdin_and_discard_extra_characters(str, ARRAY_SIZE);

        printf("The input received was \"%s\"\n", str);
        printf("Length of input received was %zu\n", strlen(str));

        printf("\n\nPlease press ENTER to continue..");
        // now clear the stdin input buffer
        get_input_from_stdin_and_discard_extra_characters(NULL, 0);

    } // end of while(1) loop

} // end of main

\$\endgroup\$
3
  • \$\begingroup\$ Should it be something similar to this: c-for-dummies.com/blog/?p=1112? \$\endgroup\$
    – convert
    Feb 26, 2022 at 19:29
  • \$\begingroup\$ @convert Your referenced code allows a nefarious or errant user to consume endless amounts of memory. This code here works with a buffer and is a more defensive coding practice. \$\endgroup\$ Mar 5, 2022 at 11:44
  • \$\begingroup\$ @chux - Reinstate Monica I wrote similar, not the same. \$\endgroup\$
    – convert
    Mar 5, 2022 at 13:31

2 Answers 2

2
\$\begingroup\$

The code is very readable, but could use more functions. There are bugs in the function char *get_input_from_stdin_and_discard_extra_characters(char *str, long size), if the size is less than zero or the output buffer is NULL the input is not read at all, this is inconsistent with what the function does when the size passed in is zero. The function should read to the end of the line in all cases.

Each of the test cases in main() should be a function that returns whether the test passed or failed.

DRY Code

There is a programming principle called the Don't Repeat Yourself Principle sometimes referred to as DRY code. If you find yourself repeating the same code multiple times it is better to encapsulate it in a function. If it is possible to loop through the code that can reduce repetition as well.

This code is repeated in the function char *get_input_from_stdin_and_discard_extra_characters(char *str, long size):

    while ((c = getchar()) && (c != '\n') && (c != EOF));

Rather than repeating the code it could be a function, or the logic can be rearranged.

\$\endgroup\$
1
\$\begingroup\$

OP employed good practices

  • Function description in the .h file and not redundantly in the .c file.

  • Well formatted - looks auto-formatted.

  • Good use of #include "get_input_from_stdin.h" before other includes in get_input_from_stdin.c.

Line of only "\n" unfortunately the same as end-of-file

When the user enters a line of only "\n", the result is the same as end-of-file.

To be more like fgets(), 1) return NULL when there is no input and 2) return NULL on input error.

Presently code obliges:

// To read all input with OP's code
while (get_input_from_stdin_and_discard_extra_characters(buf, sizeof buf) && 
    !feof(stdin) && !ferror(stdin)) {
  puts(buf);
}

By matching fgets() functionality:

// With updated code that detects end-of-file and input error
while (get_input_from_stdin_and_discard_extra_characters(buf, sizeof buf)) {
  puts(buf);
}

long vs. size_t

Code use long for long size to pass the size of the buffer. Array sizes and indexes are well handled by size_t, an unsigned type, as being neither too narrow a type nor too wide. Although long is unlikely to be too narrow, the sign-ness differs. This will flag unnecessarily warnings on when calling code uses size_t arguments. e.g. warning: conversion to 'long int' from 'size_t' {aka 'long unsigned int'} may change the sign of the result [-Wsign-conversion].

Note the str...() functions uses size_ vs. int or long for size.

Pathological cases

As is, "In all cases, reading input stops after encountering a newline ('\n') or EOF * ", is not true is all cases.

I'd recommend to handle all if (size == 0), if (!str), if (size < 0) the same: read the entire line and return NULL without attempting to change the buffer.

Minor: Function comment

In the .c file, consider a comment before the function to show where to find detailed info. Example:

// See get_input_from_stdin.h
char *get_input_from_stdin_and_discard_extra_characters(char *str, long size) {

Minor: Name length

Function name is quite long.

Minor: subtract once

Instead of

for (i = 0; i < (size - 1); i = i + 1) {

Consider

size--;
for (i = 0; i < size; i = i + 1) {

This likely makes no difference with a good compiler, yet helps a weaker one.

Minor: DRYer code

// str[i] = (char)(c);
str[i] = (char) c;

Coder parameter re-order

The 2nd form is gaining preference as it self documents size role and allows various analyzers to detect issues.

Note: C2x is planned to require supporting the VLAs in function signatures.

char *get_input_from_stdin_and_discard_extra_characters(char *str, long size)

// vs

// w/ VLA support
char *get_input_from_stdin_and_discard_extra_characters(long size, char str[size])
// w/o VLA support
char *get_input_from_stdin_and_discard_extra_characters(long size, char str[/* size */])

Better than usual read_a_line function


Sample alternative

Incorporating above ideas and a few more:

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

char* get_input_from_stdin_alt(size_t size, char *str) {
  // Handle pathological cases.
  if (str == NULL) {
    size == 0;
  }
  if (size == 0) {
    str = NULL;  
  }

  // Only needed when encoding is not certainly 2's compliment
  unsigned char *ustr = (unsigned char *) str;

  size_t len = size > 0 ? size - 1 : 0;
  size_t i = 0;
  int ch;
  bool nothing_read = true; // Nothing read except maybe a '\n'

  while ((ch = getchar()) != EOF && ch != '\n') {
    nothing_read = false;
    if (i < len) {
      ustr[i++] = (unsigned char) ch;
    }
  }

  if (size > 0) {
    ustr[i] = '\0';
  }

  // If nothing read or an input error occurred ...
  if (ch == EOF && (nothing_read || !feof(stdin))) {
    return NULL;
  }

  return str;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.