CoreTechED

Loading

Master C fundamentals with practice problems: variables, data types, I/O, operators, conditionals, and loops. Full C99 code with edge cases.

C Fundamental Practice Problems: Complete Guide

C Fundamental Practice Problems: A Comprehensive Tutorial

Learning C is not about memorizing syntax—it’s about building muscle memory through practice. This tutorial is a structured practice guide covering the fundamental concepts every C programmer must master: variables, data types, input/output, format specifiers, operators, conditional statements, and loops.

Each section explains the concept, then walks through a representative practice problem with a complete, portable C99/C11 solution and expected output.


Conceptual Introduction & Fundamentals

Why Practice Fundamentals?

Fundamentals are the foundation of every C program, whether it’s 20 lines or 20,000. Weak fundamentals show up as:

  • Off-by-one errors in loops.
  • Format specifier mismatches causing undefined behavior.
  • Buffer overflows from unsafe input functions.
  • Silent type conversion bugs in arithmetic.
  • Incorrect edge-case handling in conditionals.

The Fundamental Topics

TopicPurpose
VariablesNamed storage for values
Data TypesSize and interpretation of values
Input / OutputCommunicating with the user
Format SpecifiersControlling how data is printed or read
OperatorsArithmetic, relational, logical, bitwise
Conditional StatementsBranching logic (if, switch)
LoopsRepetition (for, while, do-while)

Difficulty Progression

  • Easy: Printing, variables, simple arithmetic, I/O.
  • Medium: Control flow, digit manipulation, number theory basics.

Syntax & Core Mechanics

1. Variables and Data Types

c

int age = 25; double price = 19.99; char grade = 'A';

Use int for whole numbers, double for decimals, char for single characters. Prefer double over float for computation; use long long when values may exceed int.

2. Input with fgets and Parsing

Never use scanf("%s", ...) or gets for strings. Prefer fgets plus strtol/strtod for robust parsing.

c

char buffer[64]; if (fgets(buffer, sizeof(buffer), stdin) == NULL) return 1; int value = (int)strtol(buffer, NULL, 10);

3. Output with printf

Match the specifier to the type:

TypeSpecifier
int%d
unsigned int%u
long long%lld
double%f, %lf, %g
char%c
String%s
size_t%zu
Pointer%p

4. Operators

c

int a = 7, b = 3; int q = a / b; /* 2 — integer division */ int r = a % b; /* 1 */ double f = (double)a / b; /* 2.333... */

Use casting to force floating-point division.

5. Conditionals and Loops

c

if (a > b) { /* ... */ } else if (a == b) { /* ... */ } else { /* ... */ } for (int i = 0; i < n; i++) { /* ... */ } while (cond) { /* ... */ } do { /* ... */ } while (cond);

Full Practical Code Example with Explanation

Below are eight representative problems covering the fundamentals, each a self-contained C99 program with verified output.

Problem 1: Print “GeeksforGeeks” with Quotes

Goal: Print a string including literal double quotes.

c

#include <stdio.h> int main(void) { /* Escape double quotes with backslash */ printf("\"GeeksforGeeks\"\n"); return 0; }

Expected output:

text

"GeeksforGeeks"

Notes: \" inside a string literal produces a literal ".

Problem 2: Swap Two Numbers (Without Temp for Integers)

Goal: Swap a and b.

c

#include <stdio.h> int main(void) { int a = 5, b = 7; /* Arithmetic swap (works for ints; watch overflow) */ a = a + b; b = a - b; a = a - b; printf("a = %d, b = %d\n", a, b); return 0; }

Expected output:

text

a = 7, b = 5

Notes: For large values, prefer a temporary variable or XOR swap. XOR is safe for equal values but not for aliasing.

Problem 3: Convert double to int

Goal: Truncate a floating-point number.

c

#include <stdio.h> int main(void) { double d = 3.99; int i = (int)d; /* explicit cast truncates toward zero */ printf("%d\n", i); return 0; }

Expected output:

text

3

Notes: (int) truncates; round() from <math.h> rounds to nearest.

Problem 4: Last Digit of a Number

Goal: Extract the last decimal digit.

c

#include <stdio.h> #include <stdlib.h> int main(void) { int n = 12345; int last = n % 10; /* Works for non-negative; see notes for negatives */ printf("%d\n", last); return 0; }

Expected output:

text

5

Notes: For negative n, % in C99 yields a non-positive remainder. Use abs(n) % 10 or n % 10 + 10 (mod 10) for a non-negative digit.

Problem 5: Compare Two Numbers

Goal: Print which of two numbers is greater.

c

#include <stdio.h> int main(void) { int a = 10, b = 20; if (a > b) printf("%d is greater\n", a); else if (b > a) printf("%d is greater\n", b); else printf("Both are equal\n"); return 0; }

Expected output:

text

20 is greater

Problem 6: FizzBuzz (1 to N)

Goal: Print Fizz, Buzz, or FizzBuzz per divisibility rule.

c

#include <stdio.h> void fizzbuzz(int n) { for (int i = 1; i <= n; i++) { if (i % 15 == 0) printf("FizzBuzz "); else if (i % 3 == 0) printf("Fizz "); else if (i % 5 == 0) printf("Buzz "); else printf("%d ", i); } printf("\n"); } int main(void) { fizzbuzz(15); return 0; }

Expected output:

text

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz

Notes: Check i % 15 == 0 first, otherwise the individual cases shadow the combined one.

Problem 7: Multiplication Table

Goal: Print an n × n multiplication table.

c

#include <stdio.h> int main(void) { int n = 5; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { printf("%4d", i * j); } printf("\n"); } return 0; }

Expected output:

text

 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25

Problem 8: Sum of N Numbers with Safe Input

Goal: Read n integers from stdin, sum them.

c

#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <limits.h> int readInteger(const char *prompt, int *outValue) { char buffer[64]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < INT_MIN || val > INT_MAX) return -1; *outValue = (int)val; return 0; } int main(void) { int n; if (readInteger("How many numbers? ", &n) != 0 || n <= 0) { fprintf(stderr, "Invalid count.\n"); return 1; } long long sum = 0; for (int i = 0; i < n; i++) { int x; char prompt[40]; snprintf(prompt, sizeof(prompt), "Number %d: ", i + 1); if (readInteger(prompt, &x) != 0) { fprintf(stderr, "Invalid number.\n"); return 1; } sum += x; } printf("Sum = %lld\n", sum); return 0; }

Sample run:

text

How many numbers? 3 Number 1: 10 Number 2: 20 Number 3: 30 Sum = 60

Notes: Uses long long for the sum to prevent overflow; strtol for robust parsing.

Bonus Problem: Medium — Count Digits of an Integer

c

#include <stdio.h> #include <stdlib.h> int countDigits(long long n) { if (n == 0) return 1; if (n < 0) n = -n; int count = 0; while (n > 0) { count++; n /= 10; } return count; } int main(void) { printf("%d\n", countDigits(0)); /* 1 */ printf("%d\n", countDigits(12345)); /* 5 */ printf("%d\n", countDigits(-9876)); /* 4 */ return 0; }

Expected output:

text

1 5 4

Bonus Problem: Medium — Leap Year

c

#include <stdio.h> int isLeapYear(int year) { /* A year is leap if divisible by 4 but not 100, unless divisible by 400 */ return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } int main(void) { int years[] = {1900, 2000, 2024, 2023, 2100}; for (int i = 0; i < 5; i++) { printf("%d → %s\n", years[i], isLeapYear(years[i]) ? "Leap" : "Not Leap"); } return 0; }

Expected output:

text

1900 → Not Leap 2000 → Leap 2024 → Leap 2023 → Not Leap 2100 → Not Leap

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Division Truncation

5 / 2 is 2, not 2.5. Cast one operand.

c

double r = (double)a / b;

2. Format Specifier Mismatch

printf("%d", 3.14) is undefined behavior. Match specifiers to types.

3. Negative Remainders

In C99, -7 % 3 == -1, not 2. Use (n % m + m) % m for a non-negative result.

4. Off-by-One in Loops

for (i = 0; i <= n; i++) iterates n+1 times. Use < unless you need inclusive bounds.

5. Unsafe Input

Never use gets (removed in C11). Prefer fgets with size limits.

6. Uninitialized Variables

Reading before assignment is undefined behavior.

c

int x; printf("%d\n", x); /* BUG */

7. Signed/Unsigned Confusion

Mixing signed and unsigned in comparisons yields surprising results.

c

int a = -1; unsigned int b = 1; if (a < b) { /* may be FALSE due to conversion */ }

8. Overflow

int overflows for large sums. Use long long.

c

long long sum = 0;

9. Character Classifications

Use <ctype.h> functions for portability.

c

if (isdigit((unsigned char)c)) { /* ... */ }

Cast to unsigned char before passing.

10. Memory Considerations

  • Fundamental programs rarely allocate memory.
  • When they do, prefer dynamic allocation for large inputs.
  • Free everything you malloc.
  • Never return pointers to stack variables.

Summary & Best Practices

Fundamentals are the platform on which all C skill is built. Practice them until they become reflexive.

Key Takeaways

  • Variables and types define what can be stored.
  • Format specifiers define how data is printed/read.
  • Operators and control flow define how programs compute.
  • Safe input requires fgets + strtol/strtod.
  • Every bug you encounter at scale is a fundamentals bug in disguise.

Best Practices

  1. Compile with -Wall -Wextra -pedantic -std=c11 on every exercise.
  2. Match format specifiers to types.
  3. Use long long for sums; double for decimals.
  4. Prefer fgets over scanf("%s", ...).
  5. Cast before division when you need floating-point results.
  6. Initialize all variables.
  7. Handle negative remainders explicitly.
  8. Cast char to unsigned char for <ctype.h> functions.
  9. Test boundary cases: zero, negatives, maxima, empty input.
  10. Practice daily — fundamentals are perishable without repetition.

Recommended Practice Progression

  • Easy: Print GeeksforGeeks; Global Variable; Swap Numbers; Convert double to int; Input Integer; Print with Space/Without/Separator/New Line; Be Precise; Print with Quotes; Last Digit; Compute a/b; Compare Two Numbers; Sign Check; Big Number; Sum of N; Multiplication Table.
  • Medium: AP Term; GP Term; Even Odd Game; Greatest of Three; Leap Year; FizzBuzz; Calculator; First Digit; Count Digits; Days Before n Days; Prime Factorization; Next Prime.

Work through each exercise with the templates and safety rules above. By the time you finish, fundamentals become second nature.

Learn octal literals in C: leading zero syntax, valid digits, conversion, printing with %o, and common pitfalls. Master base-8 integer constants in C.

Octal Literals in C: Complete Guide & Examples

Octal Literals in C: A Comprehensive Tutorial

Octal literals are integer constants expressed in base-8 notation. In C, any integer literal that begins with a leading 0 (zero) is interpreted as an octal number, not a decimal number. Understanding octal literals is crucial for avoiding subtle bugs and for working with low-level systems programming where octal representation is still used (e.g., Unix file permissions).

This tutorial provides an in-depth exploration of octal literals in C: their syntax, conversion rules, valid digits, practical applications, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is an Octal Literal?

An octal literal is a way to represent an integer value using base-8 notation. The octal number system uses eight digits: 0, 1, 2, 3, 4, 5, 6, 7. Each position in an octal number represents a power of 8.

In C, an octal literal is written by prefixing the number with 0 (zero). For example:

c

int decimal = 10; /* decimal literal: value 10 */ int octal = 010; /* octal literal: value 8 (1*8 + 0) */

Why Use Octal Literals?

Octal literals are less common in modern application programming but remain important in:

  • Unix/Linux file permissions: Permission bits are often expressed in octal (e.g., 0755 for rwxr-xr-x).
  • Embedded systems: Hardware registers and bit masks are sometimes documented in octal.
  • Legacy code: Older C programs may use octal constants.
  • Compact representation: Octal can represent groups of three bits more compactly than binary.

Octal vs. Decimal vs. Hexadecimal

BasePrefixValid DigitsExampleDecimal Value
Decimalnone0-91010
Octal00-70108
Hexadecimal0x or 0X0-9, a-f, A-F0x1016
Binary (C23/GCC)0b or 0B0-10b102

Historical Context

Octal was widely used in early computing because many systems had word sizes that were multiples of three bits (e.g., 9-bit, 12-bit, 18-bit, 36-bit). The Unix operating system, which C was developed alongside, still uses octal for file permissions and some other settings.


Syntax & Core Mechanics

Basic Syntax

An octal literal in C is written as:

text

0 followed by zero or more octal digits (0-7)

Examples:

c

0 /* octal zero (same as decimal zero) */ 07 /* octal 7 (same as decimal 7) */ 010 /* octal 10 = decimal 8 */ 077 /* octal 77 = decimal 63 */ 012345 /* octal 12345 = decimal 5349 */

Conversion: Octal to Decimal

To convert an octal number to decimal, multiply each digit by 8 raised to the power of its position (from right to left, starting at 0).

Example: 0101 (octal)

text

1 * 8^0 = 1 * 1 = 1 0 * 8^1 = 0 * 8 = 0 1 * 8^2 = 1 * 64 = 64 Total: 64 + 0 + 1 = 65

So 0101 in C is decimal 65.

Valid and Invalid Octal Literals

Valid octal literals:

c

int a = 0; /* 0 */ int b = 07; /* 7 */ int c = 010; /* 8 */ int d = 0777; /* 511 */ int e = 012345; /* 5349 */

Invalid octal literals:

c

int f = 08; /* Error: 8 is not a valid octal digit */ int g = 09; /* Error: 9 is not a valid octal digit */ int h = 0x10; /* This is hexadecimal, not octal */ int i = 10; /* This is decimal, not octal */

Suffixes with Octal Literals

Octal literals can use the same suffixes as decimal integers to specify type:

c

010U /* unsigned int octal */ 010L /* long int octal */ 010UL /* unsigned long octal */ 010LL /* long long octal */ 010ULL /* unsigned long long octal */

Example:

c

#include <stdio.h> int main(void) { unsigned int perms = 0755U; /* typical Unix file permission */ long int bigOctal = 077777L; /* large octal value */ printf("perms = %u (octal: %o)\n", perms, perms); printf("bigOctal = %ld (octal: %lo)\n", bigOctal, bigOctal); return 0; }

Expected output:

text

perms = 493 (octal: 755) bigOctal = 32767 (octal: 77777)

Printing Octal Values

Use the %o format specifier to print an integer in octal notation without the leading zero. Use %#o to include the leading zero.

c

#include <stdio.h> int main(void) { int value = 65; printf("Decimal: %d\n", value); printf("Octal: %o\n", value); printf("Octal with prefix: %#o\n", value); return 0; }

Expected output:

text

Decimal: 65 Octal: 101 Octal with prefix: 0101

Full Practical Code Example with Explanation

This program demonstrates octal literals in a realistic scenario: a Unix-style file permission manager. It uses octal literals to represent permission bits, combines them with bitwise operations, and displays permissions in both octal and symbolic notation.

Program: File Permission Manager

c

/** * file_permissions.c * Demonstrates octal literals in C using Unix-style file permissions. * Shows how octal constants represent read/write/execute bits. */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* Octal literals for permission bits */ #define PERM_OWNER_READ 0400 /* Owner read permission */ #define PERM_OWNER_WRITE 0200 /* Owner write permission */ #define PERM_OWNER_EXEC 0100 /* Owner execute permission */ #define PERM_GROUP_READ 0040 /* Group read permission */ #define PERM_GROUP_WRITE 0020 /* Group write permission */ #define PERM_GROUP_EXEC 0010 /* Group execute permission */ #define PERM_OTHER_READ 0004 /* Other read permission */ #define PERM_OTHER_WRITE 0002 /* Other write permission */ #define PERM_OTHER_EXEC 0001 /* Other execute permission */ /* Common permission combinations */ #define PERM_DEFAULT 0644 /* Owner: rw, Group: r, Other: r */ #define PERM_EXECUTABLE 0755 /* Owner: rwx, Group: r-x, Other: r-x */ #define PERM_PRIVATE 0600 /* Owner: rw, Group: none, Other: none */ #define PERM_FULL 0777 /* Everyone: rwx */ /* Function prototypes */ void printPermissionsOctal(int perms); void printPermissionsSymbolic(int perms); int parseOctalInput(const char *input); int main(void) { int permissions; char inputBuffer[32]; char *endPtr; /* Demonstrate predefined octal permission literals */ printf("=== Common File Permissions ===\n"); printf("Default (0644): "); printPermissionsOctal(PERM_DEFAULT); printPermissionsSymbolic(PERM_DEFAULT); printf("\n"); printf("Executable (0755): "); printPermissionsOctal(PERM_EXECUTABLE); printPermissionsSymbolic(PERM_EXECUTABLE); printf("\n"); printf("Private (0600): "); printPermissionsOctal(PERM_PRIVATE); printPermissionsSymbolic(PERM_PRIVATE); printf("\n"); printf("Full (0777): "); printPermissionsOctal(PERM_FULL); printPermissionsSymbolic(PERM_FULL); printf("\n"); /* Allow user to enter custom permissions in octal */ printf("\n=== Custom Permissions ===\n"); printf("Enter permissions in octal (e.g., 0754): "); fflush(stdout); if (fgets(inputBuffer, sizeof(inputBuffer), stdin) == NULL) { fprintf(stderr, "Error reading input.\n"); return EXIT_FAILURE; } /* Remove trailing newline */ inputBuffer[strcspn(inputBuffer, "\n")] = '\0'; /* Parse octal input using strtol with base 8 */ errno = 0; permissions = (int)strtol(inputBuffer, &endPtr, 8); /* Validate input */ if (endPtr == inputBuffer || *endPtr != '\0') { fprintf(stderr, "Invalid octal number. Use digits 0-7 only.\n"); return EXIT_FAILURE; } if (permissions < 0 || permissions > 0777) { fprintf(stderr, "Permissions must be between 0000 and 0777.\n"); return EXIT_FAILURE; } /* Display entered permissions */ printf("\nEntered permissions:\n"); printf("Octal: "); printPermissionsOctal(permissions); printf("\nSymbolic: "); printPermissionsSymbolic(permissions); printf("\n"); /* Demonstrate bitwise operations with octal literals */ printf("\n=== Bitwise Operations ===\n"); int base = 0600; /* Owner: rw */ int addGroupRead = base | PERM_GROUP_READ; /* Add group read */ int removeOwnerWrite = base & ~PERM_OWNER_WRITE; /* Remove owner write */ printf("Base (0600): "); printPermissionsSymbolic(base); printf("\n"); printf("Add group read (0640): "); printPermissionsSymbolic(addGroupRead); printf("\n"); printf("Remove owner write (0400): "); printPermissionsSymbolic(removeOwnerWrite); printf("\n"); return EXIT_SUCCESS; } /* Prints permissions in octal notation with leading zero */ void printPermissionsOctal(int perms) { printf("%04o", perms); /* %04o prints 4 digits with leading zeros */ } /* Prints permissions in symbolic notation (e.g., rwxr-xr-x) */ void printPermissionsSymbolic(int perms) { char symbols[10]; int index = 0; /* Owner */ symbols[index++] = (perms & PERM_OWNER_READ) ? 'r' : '-'; symbols[index++] = (perms & PERM_OWNER_WRITE) ? 'w' : '-'; symbols[index++] = (perms & PERM_OWNER_EXEC) ? 'x' : '-'; /* Group */ symbols[index++] = (perms & PERM_GROUP_READ) ? 'r' : '-'; symbols[index++] = (perms & PERM_GROUP_WRITE) ? 'w' : '-'; symbols[index++] = (perms & PERM_GROUP_EXEC) ? 'x' : '-'; /* Other */ symbols[index++] = (perms & PERM_OTHER_READ) ? 'r' : '-'; symbols[index++] = (perms & PERM_OTHER_WRITE) ? 'w' : '-'; symbols[index++] = (perms & PERM_OTHER_EXEC) ? 'x' : '-'; symbols[index] = '\0'; printf("%s", symbols); }

Explanation of Octal Literal Usage

  1. Permission constants: All permission macros (PERM_OWNER_READ, PERM_DEFAULT, etc.) are defined using octal literals. This is standard practice in Unix/C programming because permission bits naturally group into threes (owner, group, other).
  2. strtol with base 8: The user input is parsed as an octal number using strtol(inputBuffer, &endPtr, 8). This allows the user to enter 0754 and have it interpreted as octal.
  3. Bitwise operations: Octal literals are combined using bitwise OR (|) and AND with complement (& ~) to add or remove permission bits.
  4. Printing octal: %04o prints the permission value with leading zeros and a width of 4, making it clear it’s octal.

Expected Output

text

=== Common File Permissions === Default (0644): 0644 rw-r--r-- Executable (0755): 0755 rwxr-xr-x Private (0600): 0600 rw------- Full (0777): 0777 rwxrwxrwx === Custom Permissions === Enter permissions in octal (e.g., 0754): 0754 Entered permissions: Octal: 0754 Symbolic: rwxr-xr-- === Bitwise Operations === Base (0600): rw------- Add group read (0640): rw-r----- Remove owner write (0400): r-------

Common Pitfalls, Edge Cases & Memory Considerations

1. Leading Zero Means Octal, Not Decimal

The most common and dangerous pitfall: adding a leading zero for formatting (e.g., 010 to mean 10) results in an octal value.

c

int x = 010; /* 8 decimal, not 10! */ int y = 10; /* 10 decimal */ printf("%d %d\n", x, y); /* prints 8 10 */

Solution: Never add leading zeros to decimal numbers unless you specifically intend octal.

2. Invalid Digits 8 and 9

Octal digits are only 0-7. Using 8 or 9 in an octal literal is a compile-time error.

c

int x = 080; /* Error: 8 is not a valid octal digit */ int y = 090; /* Error: 9 is not a valid octal digit */

Compiler error:

text

error: invalid digit "8" in octal constant

Solution: Use decimal (no prefix) or hexadecimal (0x) if you need digits 8 and 9.

3. Accidental Octal in User Input

When parsing user input with strtol using base 0 (auto-detection), a leading zero causes the input to be interpreted as octal.

c

char input[] = "0755"; int value = (int)strtol(input, NULL, 0); /* Base 0: auto-detect, treats as octal */ printf("%d\n", value); /* prints 493, not 755 */

Solution: Use base 10 explicitly if you want decimal parsing: strtol(input, NULL, 10).

4. Confusion with Decimal Point

Octal literals apply only to integers. A leading zero before a floating-point number is not octal.

c

float f = 0.5; /* This is decimal 0.5, not octal */

5. Portability of Octal Literals

Octal literals are standard C and work on all compilers. However, their interpretation (leading zero) is fixed by the standard, so there are no portability issues—only programmer confusion.

6. Using %o with Negative Numbers

Printing a negative integer with %o produces the two’s complement representation in octal (if unsigned) or implementation-defined behavior for signed.

c

int x = -8; printf("%o\n", x); /* Implementation-defined for negative */

Solution: Use unsigned int for bit patterns or cast to unsigned int before printing.

7. Octal Literals in Expressions

Octal literals participate in expressions just like any integer literal. Mixing bases is allowed but can be confusing.

c

int x = 010 + 0x10 + 10; /* 8 + 16 + 10 = 34 */

Solution: Use comments to clarify non-decimal literals: int perms = 0644; /* octal */

8. Memory Considerations

Octal literals are compile-time constants; they occupy no runtime memory as literals. They are encoded directly in instructions or data. The size of the literal follows the same rules as decimal integers (default int, or larger if needed, or modified by suffixes).

9. String Input with Leading Zeros

When accepting numeric input as strings, leading zeros may be significant. Always specify the base explicitly in parsing functions to avoid accidental octal interpretation.

c

int value = (int)strtol(input, NULL, 8); /* Explicit octal */ int value2 = (int)strtol(input, NULL, 10); /* Explicit decimal */

10. Debugging Octal Values

When debugging, use %o to print octal values to verify they match expectations.

c

int perms = 0644; printf("perms = %#o (decimal %d)\n", perms, perms);

Summary & Best Practices

Octal literals in C are integer constants expressed in base-8 notation. They are denoted by a leading zero. While less common than decimal or hexadecimal, they remain important in systems programming, especially for file permissions and hardware bit masks.

Key Takeaways

  • Octal literals start with 0 followed by digits 0-7.
  • Digits 8 and 9 are invalid in octal literals.
  • Octal is base-8; each position is a power of 8.
  • Use %o to print octal; %#o to include the leading zero.
  • Suffixes (U, L, LL) can be appended to octal literals.
  • Be cautious with user input: leading zeros may trigger octal parsing.

Best Practices

  1. Avoid leading zeros on decimal numbers unless you specifically intend octal.
  2. Use octal for Unix file permissions (e.g., 0644, 0755)—this is idiomatic.
  3. Comment non-decimal literals to clarify intent: int perms = 0644; /* octal */
  4. Use %o or %#o to print octal values for debugging.
  5. Specify base explicitly in strtol to avoid accidental octal interpretation.
  6. Use symbolic constants (e.g., #define PERM_OWNER_READ 0400) for readability.
  7. Validate user input when parsing octal numbers to ensure digits are 0-7.
  8. Prefer hexadecimal for bit masks in modern code, but be aware octal may appear in legacy code.
  9. Understand the leading zero rule to avoid subtle bugs.
  10. Use static_assert (C11) or runtime checks to verify octal constant values when correctness is critical.

By mastering octal literals, you’ll avoid a common source of bugs and be prepared to work with systems that still use octal notation, such as Unix/Linux file permissions.