CoreTechED

Loading

Learn C string functions: strlen, strcpy, strcat, strcmp, strchr, strstr, sprintf, strtok. Master safe string manipulation and avoid pitfalls.

C String Functions: Complete Guide with Examples

C String Functions: A Comprehensive Tutorial

String functions in C are built-in library functions used to perform common operations on strings, such as copying, comparing, concatenating, and searching. These functions are declared in the <string.h> header file (except sprintf, which is in <stdio.h>). Understanding string functions is essential for efficient and safe string manipulation in C.

This tutorial provides an in-depth exploration of C string functions: their syntax, usage, return values, common pitfalls, and best practices for safe string handling.


Conceptual Introduction & Fundamentals

What are String Functions?

String functions are pre-written functions that operate on C strings (null-terminated character arrays). They provide standardized, efficient implementations of common string operations, eliminating the need to write manual loops for every string task.

Why Use String Functions?

  • Efficiency: Optimized implementations by compiler/library vendors.
  • Reliability: Well-tested and standardized.
  • Readability: Code is cleaner and more maintainable.
  • Portability: Available on all standard-compliant C implementations.
  • Productivity: Write less code for common operations.

The <string.h> Header

Most string functions are declared in <string.h>. To use them:

c

#include <string.h>

Categories of String Functions

CategoryFunctions
Lengthstrlen
Copystrcpy, strncpy
Concatenatestrcat, strncat
Comparestrcmp, strncmp
Searchstrchr, strrchr, strstr
Formatsprintf (from <stdio.h>)
Tokenizestrtok

Safety Considerations

Many traditional string functions (strcpy, strcat, sprintf) do not check buffer sizes, making them prone to buffer overflows. Prefer safer alternatives:

UnsafeSafer Alternative
strcpystrncpy or snprintf
strcatstrncat or snprintf
sprintfsnprintf
getsfgets

Syntax & Core Mechanics

strlen() — String Length

Returns the number of characters in a string, excluding the null terminator.

c

#include <stdio.h> #include <string.h> int main(void) { char s[] = "Gfg"; size_t length = strlen(s); printf("Length: %zu\n", length); printf("Array size: %zu\n", sizeof(s)); /* Includes null */ return 0; }

Expected output:

text

Length: 3 Array size: 4

strcpy() — String Copy

Copies the source string (including null terminator) to the destination.

c

#include <stdio.h> #include <string.h> int main(void) { char src[] = "Hello"; char dest[20]; strcpy(dest, src); printf("dest: %s\n", dest); return 0; }

Expected output:

text

dest: Hello

strncpy() — Bounded String Copy

Copies at most n characters. If the source is shorter than n, pads with nulls.

c

#include <stdio.h> #include <string.h> int main(void) { char src[] = "Hello"; char dest[20] = "XXXXXXXXXX"; /* Pre-filled for demonstration */ strncpy(dest, src, 4); dest[4] = '\0'; /* Ensure null termination */ printf("dest: %s\n", dest); return 0; }

Expected output:

text

dest: Hell

Note: strncpy does not guarantee null termination if n <= source length. Always add null manually.

strcat() — String Concatenate

Appends source to the end of destination.

c

#include <stdio.h> #include <string.h> int main(void) { char s1[30] = "Hello, "; char s2[] = "Geeks!"; strcat(s1, s2); printf("s1: %s\n", s1); return 0; }

Expected output:

text

s1: Hello, Geeks!

strncat() — Bounded String Concatenate

Appends at most n characters from source to destination.

c

#include <stdio.h> #include <string.h> int main(void) { char s1[30] = "Hello, "; char s2[] = "Geeks!"; strncat(s1, s2, 4); printf("s1: %s\n", s1); return 0; }

Expected output:

text

s1: Hello, Geek

strcmp() — String Compare

Compares two strings lexicographically. Returns:

  • 0 if equal
  • Negative if first < second
  • Positive if first > second

c

#include <stdio.h> #include <string.h> int main(void) { char s1[] = "Apple"; char s2[] = "Applet"; int result = strcmp(s1, s2); if (result == 0) { printf("s1 and s2 are same\n"); } else if (result < 0) { printf("s1 is lexicographically smaller than s2\n"); } else { printf("s1 is lexicographically greater than s2\n"); } return 0; }

Expected output:

text

s1 is lexicographically smaller than s2

strncmp() — Bounded String Compare

Compares first n characters.

c

#include <stdio.h> #include <string.h> int main(void) { char s1[] = "Apple"; char s2[] = "Applet"; int result = strncmp(s1, s2, 4); if (result == 0) { printf("First 4 characters are same\n"); } return 0; }

Expected output:

text

First 4 characters are same

strchr() — Find First Character

Returns pointer to first occurrence of character, or NULL.

c

#include <stdio.h> #include <string.h> int main(void) { char s[] = "Hello, World!"; char *result = strchr(s, 'o'); if (result != NULL) { printf("Character found at index: %ld\n", result - s); } else { printf("Character not found\n"); } return 0; }

Expected output:

text

Character found at index: 4

strrchr() — Find Last Character

Returns pointer to last occurrence of character.

c

#include <stdio.h> #include <string.h> int main(void) { char s[] = "Hello, World!"; char *result = strrchr(s, 'o'); if (result != NULL) { printf("Character found at index: %ld\n", result - s); } return 0; }

Expected output:

text

Character found at index: 8

strstr() — Find Substring

Returns pointer to first occurrence of substring.

c

#include <stdio.h> #include <string.h> int main(void) { char s[] = "Hello, Geeks!"; char *pos = strstr(s, "Geeks"); if (pos != NULL) { printf("Found: %s\n", pos); } else { printf("Not Found\n"); } return 0; }

Expected output:

text

Found: Geeks!

sprintf() — Format String to Buffer

Formats data into a string buffer. Declared in <stdio.h>.

c

#include <stdio.h> int main(void) { char buffer[50]; int n = 10; sprintf(buffer, "The value is %d", n); printf("%s\n", buffer); return 0; }

Expected output:

text

The value is 10

Safer alternative: snprintf

c

char buffer[50]; snprintf(buffer, sizeof(buffer), "The value is %d", n);

strtok() — Tokenize String

Splits a string into tokens based on delimiters. Modifies the original string.

c

#include <stdio.h> #include <string.h> int main(void) { char s[] = "Hello, Geeks, C!"; char *token = strtok(s, ", "); while (token != NULL) { printf("%s\n", token); token = strtok(NULL, ", "); } return 0; }

Expected output:

text

Hello Geeks C!

Note: strtok modifies the original string and is not thread-safe. Use strtok_r for reentrant version (POSIX).


Full Practical Code Example with Explanation

This program demonstrates all major string functions in a realistic scenario: a simple text processing and validation system.

Program: String Functions Demo

c

/** * string_functions_demo.c * Demonstrates all major C string functions. * Includes safe usage patterns and error handling. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <stdbool.h> #define MAX_LINE_LEN 200 #define MAX_STR_LEN 100 /* Function prototypes */ void demonstrateLength(void); void demonstrateCopy(void); void demonstrateConcatenate(void); void demonstrateCompare(void); void demonstrateSearch(void); void demonstrateFormat(void); void demonstrateTokenize(void); int readInteger(const char *prompt, int *outValue); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== C String Functions Demo ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. strlen - String Length\n"); printf("2. strcpy/strncpy - String Copy\n"); printf("3. strcat/strncat - String Concatenate\n"); printf("4. strcmp/strncmp - String Compare\n"); printf("5. strchr/strrchr/strstr - Search\n"); printf("6. sprintf - Format String\n"); printf("7. strtok - Tokenize String\n"); printf("8. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateLength(); break; case 2: demonstrateCopy(); break; case 3: demonstrateConcatenate(); break; case 4: demonstrateCompare(); break; case 5: demonstrateSearch(); break; case 6: demonstrateFormat(); break; case 7: demonstrateTokenize(); break; case 8: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Demonstrates strlen */ void demonstrateLength(void) { printf("\n=== strlen ===\n"); char str1[] = "Hello"; char str2[] = ""; char str3[] = "A"; printf("strlen(\"%s\") = %zu\n", str1, strlen(str1)); printf("strlen(\"%s\") = %zu\n", str2, strlen(str2)); printf("strlen(\"%s\") = %zu\n", str3, strlen(str3)); printf("\nManual length calculation:\n"); size_t len = 0; while (str1[len] != '\0') { len++; } printf("Manual strlen(\"%s\") = %zu\n", str1, len); } /* Demonstrates strcpy and strncpy */ void demonstrateCopy(void) { printf("\n=== strcpy / strncpy ===\n"); char src[] = "Hello World"; char dest1[50]; char dest2[20] = "XXXXXXXXXXXXXXXX"; /* Pre-filled */ /* strcpy */ strcpy(dest1, src); printf("strcpy: dest1 = \"%s\"\n", dest1); /* strncpy with explicit null termination */ strncpy(dest2, src, 5); dest2[5] = '\0'; /* Ensure null termination */ printf("strncpy (5 chars): dest2 = \"%s\"\n", dest2); /* strncpy when source is shorter */ char shortSrc[] = "Hi"; char dest3[10] = "XXXXXXXXX"; strncpy(dest3, shortSrc, 5); /* Remaining characters are padded with nulls */ printf("strncpy (short source): dest3[0] = '%c', dest3[1] = '%c', dest3[2] = '%d'\n", dest3[0], dest3[1], dest3[2]); } /* Demonstrates strcat and strncat */ void demonstrateConcatenate(void) { printf("\n=== strcat / strncat ===\n"); char s1[50] = "Hello, "; char s2[] = "Geeks!"; char s3[50] = "Hello, "; /* strcat */ strcat(s1, s2); printf("strcat: \"%s\"\n", s1); /* strncat */ strncat(s3, s2, 4); printf("strncat (4 chars): \"%s\"\n", s3); /* Safe concatenation using snprintf */ char safe[50]; snprintf(safe, sizeof(safe), "%s%s", "Hello, ", "Geeks!"); printf("snprintf: \"%s\"\n", safe); } /* Demonstrates strcmp and strncmp */ void demonstrateCompare(void) { printf("\n=== strcmp / strncmp ===\n"); char s1[] = "Apple"; char s2[] = "Applet"; char s3[] = "apple"; printf("strcmp(\"%s\", \"%s\") = %d\n", s1, s2, strcmp(s1, s2)); printf("strcmp(\"%s\", \"%s\") = %d\n", s1, s3, strcmp(s1, s3)); printf("strcmp(\"%s\", \"%s\") = %d\n", s1, s1, strcmp(s1, s1)); printf("strncmp(\"%s\", \"%s\", 4) = %d\n", s1, s2, strncmp(s1, s2, 4)); printf("strncmp(\"%s\", \"%s\", 5) = %d\n", s1, s2, strncmp(s1, s2, 5)); } /* Demonstrates search functions */ void demonstrateSearch(void) { printf("\n=== Search Functions ===\n"); char s[] = "Hello, World! Hello again!"; /* strchr */ char *first_o = strchr(s, 'o'); if (first_o != NULL) { printf("strchr: first 'o' at index %ld\n", first_o - s); } /* strrchr */ char *last_o = strrchr(s, 'o'); if (last_o != NULL) { printf("strrchr: last 'o' at index %ld\n", last_o - s); } /* strstr */ char *substring = strstr(s, "World"); if (substring != NULL) { printf("strstr: \"World\" found at index %ld\n", substring - s); printf(" Remaining: \"%s\"\n", substring); } /* strchr with character not in string */ char *notFound = strchr(s, 'z'); if (notFound == NULL) { printf("strchr: 'z' not found (NULL returned)\n"); } } /* Demonstrates sprintf */ void demonstrateFormat(void) { printf("\n=== sprintf / snprintf ===\n"); char buffer[100]; int age = 25; double score = 95.5; char name[] = "Alice"; /* sprintf */ sprintf(buffer, "Name: %s, Age: %d, Score: %.2f", name, age, score); printf("sprintf: \"%s\"\n", buffer); /* snprintf (safer) */ snprintf(buffer, sizeof(buffer), "Name: %s, Age: %d, Score: %.2f", name, age, score); printf("snprintf: \"%s\"\n", buffer); /* snprintf with truncation */ snprintf(buffer, 10, "Name: %s", "VeryLongNameHere"); printf("snprintf (truncated): \"%s\"\n", buffer); } /* Demonstrates strtok */ void demonstrateTokenize(void) { printf("\n=== strtok ===\n"); char s[] = "Hello, Geeks, C, Programming!"; char *delimiter = ", "; printf("Original string: \"%s\"\n", s); printf("Tokens:\n"); char *token = strtok(s, delimiter); int tokenCount = 0; while (token != NULL) { printf(" Token %d: \"%s\"\n", ++tokenCount, token); token = strtok(NULL, delimiter); } printf("After strtok, original string: \"%s\"\n", s); printf("(Original string has been modified)\n"); }

Explanation of String Functions

  1. strlen: Counts characters until null.
  2. strcpy / strncpy: Copies strings; strncpy requires manual null termination.
  3. strcat / strncat: Appends strings; strncat is safer.
  4. strcmp / strncmp: Lexicographic comparison.
  5. strchr / strrchr / strstr: Search for characters/substrings.
  6. sprintf / snprintf: Format strings into buffers.
  7. strtok: Tokenizes strings (modifies original).

Expected Output (sample run)

text

=== C String Functions Demo === --- Menu --- 1. strlen - String Length 2. strcpy/strncpy - String Copy 3. strcat/strncat - String Concatenate 4. strcmp/strncmp - String Compare 5. strchr/strrchr/strstr - Search 6. sprintf - Format String 7. strtok - Tokenize String 8. Exit Enter choice: 1 === strlen === strlen("Hello") = 5 strlen("") = 0 strlen("A") = 1 Manual length calculation: Manual strlen("Hello") = 5 --- Menu --- ... Enter choice: 7 === strtok === Original string: "Hello, Geeks, C, Programming!" Tokens: Token 1: "Hello" Token 2: "Geeks" Token 3: "C" Token 4: "Programming!" After strtok, original string: "Hello" (Original string has been modified)

Common Pitfalls, Edge Cases & Memory Considerations

1. Buffer Overflow with strcpy and strcat

These functions don’t check destination size, causing buffer overflows.

c

char dest[5]; strcpy(dest, "Hello World"); // BUG: overflow (12 chars into 5-byte buffer)

Solution: Use strncpy with manual null termination, or snprintf.

2. strncpy Not Null-Terminating

If n is less than or equal to the source length, strncpy does not add null terminator.

c

char src[] = "Hello"; char dest[5]; strncpy(dest, src, 5); // dest is not null-terminated printf("%s\n", dest); // BUG: reads past buffer

Solution: Always add null: dest[4] = '\0';

3. strlen on Uninitialized String

Calling strlen on an uninitialized string is undefined behavior.

c

char str[20]; strlen(str); // BUG: garbage data

Solution: Initialize strings: char str[20] = "";

4. strtok Modifies Original String

strtok replaces delimiters with null characters, destroying the original string.

c

char s[] = "Hello, World"; strtok(s, ", "); printf("%s\n", s); // Prints "Hello" (original modified)

Solution: Make a copy before using strtok if you need the original.

5. strtok Not Thread-Safe

strtok uses a static internal buffer, making it unsafe in multi-threaded programs.

Solution: Use strtok_r (POSIX) for thread-safe tokenization.

6. sprintf Buffer Overflow

sprintf does not check buffer size.

c

char buffer[10]; sprintf(buffer, "Value: %d", 123456789); // BUG: overflow

Solution: Use snprintf(buffer, sizeof(buffer), ...).

7. strcmp with Non-String Data

strcmp expects null-terminated strings. Passing non-string data is undefined.

c

char data[5] = {'A', 'B', 'C', 'D', 'E'}; // No null strcmp(data, "ABCDE"); // BUG: data is not a string

Solution: Ensure null termination: char data[6] = {'A', 'B', 'C', 'D', 'E', '\0'};

8. strchr Returning NULL

Dereferencing the result of strchr without checking for NULL causes crash.

c

char *result = strchr(s, 'z'); printf("%c\n", *result); // BUG: NULL dereference if not found

Solution: Always check for NULL before dereferencing.

9. String Concatenation Overhead

Repeated strcat calls in loops are inefficient (O(n²) due to repeated scanning).

Solution: Track the end position and copy directly, or use snprintf for combined formatting.

10. Memory Considerations

  • strtok uses static memory (not thread-safe).
  • strcpy and strcat do not allocate memory; they write to existing buffers.
  • Always ensure destination buffers are large enough.
  • Consider using strdup (POSIX) to create copies on the heap (remember to free).

Summary & Best Practices

C string functions provide powerful tools for string manipulation. Understanding their behavior, limitations, and safe usage patterns is essential for writing robust programs.

Key Takeaways

  • strlen returns length excluding null.
  • strcpy/strncpy copy strings; strncpy needs manual null termination.
  • strcat/strncat concatenate strings; strncat is safer.
  • strcmp/strncmp compare strings lexicographically.
  • strchr/strrchr/strstr search for characters/substrings.
  • sprintf/snprintf format strings (prefer snprintf).
  • strtok tokenizes strings (modifies original, not thread-safe).

Best Practices

  1. Prefer snprintf over sprintf for safe formatting.
  2. Use strncpy with manual null termination or snprintf instead of strcpy.
  3. Use strncat instead of strcat for bounded concatenation.
  4. Always check for NULL from search functions before dereferencing.
  5. Make a copy before using strtok if the original string is needed.
  6. Use strtok_r in multi-threaded programs.
  7. Ensure destination buffers are large enough for all operations.
  8. Initialize strings before using string functions.
  9. Use sizeof for buffer capacity when calling snprintf or fgets.
  10. Enable compiler warnings (-Wall -Wextra) to catch potential issues.

By mastering C string functions and following safe practices, you’ll write robust programs that handle text processing efficiently without buffer overflows or undefined behavior.

Learn C input/output with printf, fputs, scanf, fgets, and getchar. Safe I/O practices, format specifiers, error handling, and common pitfalls explained.

Basic Input and Output in C: Complete Tutorial

Basic Input and Output in C: A Comprehensive Tutorial

Input and output (I/O) operations are fundamental to C programming. They enable communication between a program and its environment: reading data from users, files, or devices, and displaying results. In C, I/O is handled through the standard library, primarily declared in <stdio.h>. This tutorial covers the core I/O functions, their syntax, safe usage, common pitfalls, and best practices for writing robust C code.


Conceptual Introduction & Fundamentals

What are Input and Output in C?

Input operations allow a program to receive data from the outside world (keyboard, files, network). Output operations send data from the program to the user (screen, files, printers). In C, all I/O is performed on streams—abstract representations of input sources or output destinations.

Standard Streams

The C standard library defines three standard streams automatically opened when a program starts:

StreamPurposeDefault Device
stdinStandard inputKeyboard
stdoutStandard outputScreen
stderrStandard error (unbuffered)Screen

These streams are of type FILE * and are declared in <stdio.h>. They can be redirected by the shell.

Formatted vs. Unformatted I/O

  • Formatted I/O converts data to/from human-readable text using format specifiers. Functions: printf, fprintf, sprintf, scanf, fscanf, sscanf.
  • Unformatted I/O reads or writes raw bytes without conversion. Functions: fgets, fputs, getchar, putchar, fread, fwrite.

The Standard I/O Library (<stdio.h>)

The <stdio.h> header declares functions for input/output, including:

  • printf and fprintf for formatted output.
  • scanf and fscanf for formatted input (use with caution).
  • fgets and fputs for line-oriented string I/O.
  • getchar and putchar for character I/O.
  • fopen, fclose, etc., for file I/O (covered in a separate tutorial).

All functions return a value that should be checked to detect errors (e.g., EOF or NULL).


Syntax & Core Mechanics

Output: printf()

The printf function writes formatted output to stdout.

Prototype:

c

int printf(const char *format, ...);

It returns the number of characters printed, or a negative value on error.

Format specifiers are placeholders that begin with % and specify how to format the corresponding argument. Common specifiers:

SpecifierType
%d or %iSigned integer
%uUnsigned integer
%fFloating-point (decimal notation)
%e or %EFloating-point (scientific notation)
%g or %GUses %f or %e depending on value
%cCharacter
%sString (null-terminated)
%pPointer address
%x or %XHexadecimal integer
%%Literal %

Examples:

c

#include <stdio.h> int main(void) { int age = 22; double pi = 3.14159; char grade = 'A'; char name[] = "Alice"; // Print text printf("Hello, World!\n"); // Print integer printf("Age: %d\n", age); // Print double with precision printf("Pi: %.2f\n", pi); // Print character and string printf("Grade: %c, Name: %s\n", grade, name); // Print multiple values printf("%s is %d years old and scored %c.\n", name, age, grade); return 0; }

Expected output:

text

Hello, World! Age: 22 Pi: 3.14 Grade: A, Name: Alice Alice is 22 years old and scored A.

Format flags: You can control width, precision, alignment, and more. Example: %10d prints an integer right-aligned in a field of width 10; %-10s left-aligns a string; %08.3f zero-pads to width 8 with 3 decimal places.

Output: fputs()

fputs writes a string (without formatting) to a specified stream. It does not append a newline automatically.

Prototype:

c

int fputs(const char *str, FILE *stream);

To print to the console, use stdout.

Example:

c

#include <stdio.h> int main(void) { fputs("This is a string without newline.", stdout); fputs(" This continues on the same line.\n", stdout); fputs("New line after this.\n", stdout); return 0; }

Expected output:

text

This is a string without newline. This continues on the same line. New line after this.

Input: scanf()

scanf reads formatted input from stdin. It is powerful but error-prone; the C community often recommends using fgets followed by parsing functions (like strtol, strtod) for robust input.

Prototype:

c

int scanf(const char *format, ...);

It returns the number of successfully matched input items, or EOF on input failure.

Basic usage:

c

#include <stdio.h> int main(void) { int age; printf("Enter your age: "); if (scanf("%d", &age) == 1) { printf("Age is: %d\n", age); } else { printf("Invalid input.\n"); } return 0; }

Sample run:

text

Enter your age: 25 Age is: 25

Important: scanf leaves the newline in the input buffer, causing problems when mixed with fgets. It can also overflow strings if %s is used without a width limit.

Safer Input: fgets()

fgets reads a line from a stream into a buffer, including the newline (if it fits). It is the recommended way to read strings in C.

Prototype:

c

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

It reads at most n-1 characters, leaving room for the null terminator. Returns NULL on error or end-of-file.

Example:

c

#include <stdio.h> #include <string.h> int main(void) { char name[20]; printf("Enter your name: "); if (fgets(name, sizeof(name), stdin) != NULL) { // Remove trailing newline (if present) name[strcspn(name, "\n")] = '\0'; printf("Hello, %s!\n", name); } else { printf("Error reading input.\n"); } return 0; }

Sample run:

text

Enter your name: John Doe Hello, John Doe!

Character I/O: getchar() and putchar()

getchar reads a single character from stdin; putchar writes a single character to stdout.

Prototypes:

c

int getchar(void); int putchar(int c);

Example:

c

#include <stdio.h> int main(void) { int ch; printf("Enter a character: "); ch = getchar(); if (ch != EOF) { printf("You entered: "); putchar(ch); putchar('\n'); } return 0; }

Sample run:

text

Enter a character: Z You entered: Z

Full Practical Code Example with Explanation

This program demonstrates safe and robust input/output in C. It reads a user’s name, age, and height using fgets for strings and strtol/strtod for numeric parsing, then displays a formatted summary.

Program: User Information Collector

c

/** * user_info.c * Demonstrates robust input/output in C. * Uses fgets for all input and strtol/strtod for safe numeric parsing. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> #define MAX_NAME_LEN 50 #define MAX_LINE_LEN 100 /* Function prototypes */ int readInteger(const char *prompt, int *outValue); int readDouble(const char *prompt, double *outValue); int main(void) { char name[MAX_NAME_LEN]; int age; double height; char buffer[MAX_LINE_LEN]; // temporary buffer for reading lines /* Read name using fgets */ printf("Enter your full name: "); fflush(stdout); if (fgets(name, sizeof(name), stdin) == NULL) { fprintf(stderr, "Error reading name.\n"); return EXIT_FAILURE; } /* Remove trailing newline */ name[strcspn(name, "\n")] = '\0'; /* Read age with validation */ if (readInteger("Enter your age: ", &age) != 0) { fprintf(stderr, "Failed to read age.\n"); return EXIT_FAILURE; } /* Read height with validation */ if (readDouble("Enter your height in meters (e.g., 1.75): ", &height) != 0) { fprintf(stderr, "Failed to read height.\n"); return EXIT_FAILURE; } /* Output summary using printf */ printf("\n--- User Summary ---\n"); printf("Name: %s\n", name); printf("Age: %d years\n", age); printf("Height: %.2f m\n", height); return EXIT_SUCCESS; } /* Reads an integer from stdin using fgets and strtol. * Returns 0 on success, -1 on failure. * On success, stores the value in outValue. */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; while (1) { printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; // I/O error or EOF } /* Check if line is too long (no newline and buffer full) */ if (strchr(buffer, '\n') == NULL && !feof(stdin)) { /* Discard rest of line to avoid affecting next input */ int ch; while ((ch = getchar()) != '\n' && ch != EOF); printf("Input too long, please try again.\n"); continue; } /* Convert to long with error checking */ errno = 0; val = strtol(buffer, &endPtr, 10); /* Check for conversion errors */ if (endPtr == buffer) { printf("No digits found. Please enter a valid integer.\n"); continue; } /* Check for extra characters after number (except newline) */ if (*endPtr != '\n' && *endPtr != '\0') { printf("Invalid characters after number. Please try again.\n"); continue; } /* Check for out-of-range (errno set or val outside int range) */ if (errno == ERANGE || val < INT_MIN || val > INT_MAX) { printf("Number out of range for int. Please try again.\n"); continue; } *outValue = (int)val; return 0; } } /* Reads a double from stdin using fgets and strtod. * Returns 0 on success, -1 on failure. * On success, stores the value in outValue. */ int readDouble(const char *prompt, double *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; double val; while (1) { printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } /* Check for overlong line */ if (strchr(buffer, '\n') == NULL && !feof(stdin)) { int ch; while ((ch = getchar()) != '\n' && ch != EOF); printf("Input too long, please try again.\n"); continue; } errno = 0; val = strtod(buffer, &endPtr); if (endPtr == buffer) { printf("No digits found. Please enter a valid number.\n"); continue; } if (*endPtr != '\n' && *endPtr != '\0') { printf("Invalid characters after number. Please try again.\n"); continue; } if (errno == ERANGE) { printf("Number out of range for double. Please try again.\n"); continue; } *outValue = val; return 0; } }

Explanation

  • fgets for all string input: Prevents buffer overflow.
  • strtol and strtod for numeric conversion: Provide full error detection (no digits, extra characters, range errors).
  • Looping on invalid input: The helper functions keep prompting until valid input is received.
  • Handling overlong lines: Detects if input line was truncated and flushes the remaining characters to avoid affecting subsequent reads.
  • Output with printf: Uses format specifiers to display the collected data.
  • Error handling: Returns from main with EXIT_FAILURE if initial reads fail.

Expected Output

Sample interaction:

text

Enter your full name: Jane Doe Enter your age: 25 Enter your height in meters (e.g., 1.75): 1.68 --- User Summary --- Name: Jane Doe Age: 25 years Height: 1.68 m

If invalid input:

text

Enter your age: abc No digits found. Please enter a valid integer. Enter your age: 25 ...

Common Pitfalls, Edge Cases & Memory Considerations

1. Buffer Overflow with scanf("%s", ...)

Using scanf with %s reads a string until whitespace, without any length limit. A user entering more characters than the buffer can hold causes stack corruption.

c

char name[10]; scanf("%s", name); // DANGEROUS

Solution: Use fgets(name, sizeof(name), stdin) or specify width in scanf: scanf("%9s", name).

2. Mixing scanf and fgets

scanf leaves the newline character in the input buffer. A subsequent fgets will read that newline and appear to skip input.

c

int age; char name[20]; scanf("%d", &age); fgets(name, sizeof(name), stdin); // reads leftover newline

Solution: Always flush the input buffer after scanf (e.g., while ((c = getchar()) != '\n' && c != EOF);) or use fgets for all input and parse with sscanf, strtol, etc.

3. Not Checking Return Values

printf, fgets, scanf, etc., can fail. Ignoring return values may lead to using uninitialized variables or infinite loops.

c

int age; scanf("%d", &age); // If input is not a number, age is uninitialized

Solution: Always check the return value. For fgets, check for NULL. For scanf, ensure the expected number of items was read.

4. Trailing Newline with fgets

fgets includes the newline character if it fits. This can cause unexpected output or string comparison failures.

Solution: Remove the newline: str[strcspn(str, "\n")] = '\0';

5. Format String Vulnerabilities

Passing user input directly as the format string to printf is a severe security risk.

c

printf(userInput); // DANGEROUS: user can include %s, %x, etc.

Solution: Always use a fixed format string: printf("%s", userInput);

6. Integer Parsing without Range Checking

atoi or sscanf with %d may not detect overflow, leading to undefined behavior.

Solution: Use strtol with errno and range checks, as shown in the practical example.

7. Unbuffered Output and Flushing

Output to stdout may be buffered, so prompts might not appear before input is requested if using scanf after printf. This can confuse users.

Solution: Call fflush(stdout) after printing prompts when using scanf or other input functions that don’t automatically flush stdout.

8. Character Input Buffering

getchar reads one character but the user must press Enter, so the newline remains. This can cause issues in loops.

Solution: Handle newline characters explicitly, or use line-based input with fgets.

9. Using gets (Removed in C11)

gets was inherently unsafe because it cannot limit input length. It was removed from the C11 standard.

Solution: Never use gets. Use fgets.

10. Memory Considerations

  • Stack buffers: Large local arrays in functions can cause stack overflow. Use dynamic allocation (malloc) for large buffers, or declare them static.
  • Input size: Assume user input may be arbitrarily long. Always specify buffer sizes to fgets and handle truncation.
  • Return values: Proper checking prevents use of uninitialized memory, which can lead to crashes or security holes.

Summary & Best Practices

Basic input and output in C is built around the <stdio.h> library. Formatted output via printf is straightforward, but input requires care to avoid buffer overflows and parsing errors. The safest approach is to use fgets for all input and strtol/strtod for numeric conversion.

Key Takeaways

  • Use printf for formatted output; know common format specifiers.
  • Use fputs for simple string output to streams.
  • Avoid scanf for strings; use fgets instead.
  • For integers and doubles, use fgets + strtol/strtod with error checking.
  • Always check return values of I/O functions.
  • Remove trailing newlines from fgets input.
  • Flush stdout before input prompts to ensure visibility.

Best Practices

  1. Prefer fgets over scanf or gets for all user input.
  2. Use strtol, strtoul, strtod for converting strings to numbers, and check for errors.
  3. Check return values of all I/O functions; handle NULL and EOF.
  4. Limit string inputs using the buffer size parameter in fgets.
  5. Remove newline from fgets result if not needed.
  6. Flush stdout after printing prompts without newline.
  7. Never use user input as a format string in printf.
  8. Use snprintf for formatted output into a buffer to avoid overflow.
  9. For large data, use dynamic memory allocation rather than huge stack arrays.
  10. Compile with warnings (-Wall -Wextra) to catch unused return values and other issues.

By following these guidelines, you’ll write C programs that handle input and output safely and robustly, even in the face of unexpected user input.