C / Editorial solution guide

Fix Input Mismatches and Leftover Newlines

Separate complete-line acquisition from numeric parsing so failed conversions and leftover delimiters cannot silently change the next record.

About 3 min readContent reviewed

Read-only knowledge base · No public comments

Recognize the symptoms

After a numeric scanf call, a following fgets appears to read an empty name. Alternatively, invalid text makes a retry loop repeat forever, or a number prefix such as 12x is accepted unexpectedly. The behaviors point to stream consumption and validation policy, rather than a missing delay between reads.

Understand the likely causes

  • A successful %d conversion generally leaves the following newline unread. A failed matching conversion can leave the offending characters in the stream. Ignoring scanf’s conversion count then permits stale values or repeated failures. An unbounded string conversion is unsafe, and fflush(stdin) is not a portable C17 input-clearing solution.

1. Trace exactly what was consumed

Write the synthetic input as visible records, for example a number line and a name line. Mark which characters each read consumes and which remain. fgets does not invent an empty line: it may legitimately return the newline left by the previous numeric conversion. Distinguish that case from end of file by checking its return value before examining the buffer.

2. Choose one bounded record policy

For this repair, one physical line is one record, each must fit its buffer including newline and terminator, and numeric lines must contain decimal digits with no extra suffix. Longer records are rejected rather than split. A production reader may accept a final line without newline, but that requires an explicit end-of-file policy; the controlled example requires newline.

3. Acquire first, then convert and validate

The complete example uses a temporary stream holding known lab records. It checks successful bounded reads and captured newlines, parses with strtol, checks no-digit, suffix, ERANGE, and domain failures, then reads the name independently. The name is treated as data through %s. No unbounded conversion or platform-specific input flushing is required.

c Read and parse two independent records
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char number[16], name[16];
    FILE *input = tmpfile();
    if (input == NULL) return 1;
    if (fputs("12\nAda\n", input) == EOF) { fclose(input); return 1; }
    rewind(input);
    if (fgets(number, sizeof number, input) == NULL) { fclose(input); return 1; }
    char *newline = strchr(number, '\n');
    if (newline == NULL) { fclose(input); return 1; }
    *newline = '\0';
    char *end;
    errno = 0;
    long value = strtol(number, &end, 10);
    if (end == number || *end != '\0' || errno == ERANGE || value < 0 || value > 100) {
        fclose(input); return 1;
    }
    if (fgets(name, sizeof name, input) == NULL) { fclose(input); return 1; }
    newline = strchr(name, '\n');
    if (newline == NULL) { fclose(input); return 1; }
    *newline = '\0';
    printf("count=%ld name=%s\n", value, name);
    return fclose(input) == 0 ? 0 : 1;
}
Expected output
count=12 name=Ada

The first fgets consumes the number line completely, so the next read obtains Ada.

4. If retaining scanf, check and recover deliberately

If an existing interface keeps scanf, require the expected assignment count and define how the remainder of the current record is consumed after success or matching failure. A single getchar is insufficient when trailing spaces or longer garbage exist. Use a controlled loop through newline or EOF and distinguish I/O failure. Do not add a trailing whitespace directive blindly: it can wait for a later non-whitespace character.

5. Keep syntax and application range separate

A record can be syntactically numeric yet outside the application domain. Check representational range before narrowing from long to int, then enforce limits such as zero through one hundred. A caller should update its stored count only after every check succeeds. On rejection, preserve the previous valid state and report which record needs correction rather than continuing with a partially parsed prefix.

Verify the fix

The complete lab program must print count=12 name=Ada. Test a nonnumeric line, 12x, 101, an empty line, end of input, and a line at or beyond buffer capacity; none may cause stale data to be accepted. Verify that the next record begins at its intended boundary after every accepted or rejected record under the chosen recovery policy.

Keep it from recurring

  • Prefer one bounded line reader and a separate parser per record type. Document newline and truncation handling, always check library return values, and test boundary-length inputs. Keep external text out of format strings and use %ld for long values and the other specifiers matching their actual types.

Official references