Statements in C

Subpage of C Deep Dive

A close look at the C language.

Statements are the heart of imperative structure in C. Their simplicity gives the programmer direct control over computation and flow.

The most basic statement is the combination of a declaration and a definition. An example is int x = 5 where int x is the declaration and x = 5 is the definition.

Expression Statements

An expression statement consists of any valid C expression followed by a semicolon. The expression is evaluated for its side effects, and any result value is discarded.

int x;          /* Declaration expression */
x = 5;          /* assignment expression */
x++;            /* increment expression */
printf("Hello");/* function call expression */
5 + 3;          /* valid but useless - computes 8 and discards it */

Key Points:

  • Most statements in C are expression statements
  • The semicolon is the terminator, not a separator
  • Even a lone semicolon (;) is a valid (empty) statement

Compound Statements (Blocks)

A compound statement, or block, groups multiple statements and declarations together within braces { }.

{
    int x = 5;      /* declaration inside block */
    int y = 10;
    printf("%d\\n", x + y);  /* statement inside block */
    /* x and y exist only within this block */
}

Characteristics:

  • Creates a new scope: variables declared inside are local to the block
  • Can appear anywhere a single statement is expected
  • Essential for control structures that need to execute multiple statements
  • Braces create scope, even without control structures:
/* Temporary variable scope */
int main() {
    int x = 10;

    {
        int temp = x * 2;  /* temp only exists here */
        printf("Double: %d\\n", temp);
    }
    /* temp is no longer accessible here */

    return 0;
}

Selection Statements

if Statement

Executes code based on a condition. The controlling expression is evaluated and converted to boolean (0 = false, non-zero = true).

Basic form

if (condition) {
    /* executed if condition is true (non-zero) */
}

Full syntax with else

if (temperature > 30) {
    printf("It's hot outside.\\n");
} else if (temperature > 20) {
    printf("It's warm.\\n");
} else if (temperature > 10) {
    printf("It's cool.\\n");
} else {
    printf("It's cold.\\n");
}

Important Notes:

  • Dangling else problem: else binds to the nearest if
  • Use braces even for single statements to avoid bugs (however the code will work for single statements if everything is written correctly)
  • Assignment in conditions is a common pitfall:
    if (x = 5) {    /* BUG: assigns 5 to x, always true! */
    if (x == 5) {   /* CORRECT: compares x with 5 */

switch Statement

Dispatches execution based on an integer constant expression.

This means the expression must be compared to an integral type (e.g., intchar, or enum).

Thus, we cannot use a switch statement to check a string of characters or any kind of array.

This mechanism allows the compiler to potentially optimize the execution dispatch using methods like jump tables, which is often more efficient than a long chain of if-else if statements for integral value comparisons. 

switch (expression) {
    case constant1:
        /* code for constant1 */
        break;
    case constant2:
        /* code for constant2 */
        break;
    default:
        /* code if no case matches */
}

Fallthrough

In C, when a break statement is omitted, the execution "falls through" to the next case block, regardless of whether that subsequent case's condition matches the switch expression. This means the code within the subsequent case will also be executed.

This continues until a break statement is encountered, or the end of the switch statement is reached.

int day = 3;
switch (day) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        printf("Weekday\\n");
        break;
    case 6:
    case 7:
        printf("Weekend\\n");
        break;
    default:
        printf("Invalid day\\n");
}
// This code will output "Weekday"

Deliberate fallthrough

Fallthrough is intentional when used for scenarios where multiple case values should trigger the same or a cumulative set of actions.

/* Count vowels, treating uppercase and lowercase the same */
char c = 'E';
int vowel_count = 0;

switch (c) {
    case 'a':
    case 'A':
    case 'e':
    case 'E':
    case 'i':
    case 'I':
    case 'o':
    case 'O':
    case 'u':
    case 'U':
        vowel_count++;
        break;
    default:
        /* not a vowel */
}

Critical Rules for switch:

  1. Expression must be of integer type (int, char, enum)
  2. Case labels must be constant expressions (no variables)
  3. Fallthrough is implicit - you must use break to exit
  4. default: is optional but good practice
  5. Variables must be declared at block scope within cases:
switch (x) {
    case 1:
        int y = 10;  /* ERROR if no braces */
        break;
    case 2:
        {
            int z = 20;  /* CORRECT: within braces */
            printf("%d\\n", z);
        }
        break;
}

Iteration Statements

while Loop

A pre-test loop - condition is checked before each iteration.

/* Countdown example */
int count = 10;
while (count > 0) {
    printf("%d\\n", count);
    count--;
}
printf("Blastoff!\\n");

/* Reading until sentinel value */
int value, sum = 0;
printf("Enter numbers (0 to stop): ");
scanf("%d", &value);
while (value != 0) {
    sum += value;
    scanf("%d", &value);
}
printf("Sum: %d\\n", sum);

do-while Loop

A post-test loop - condition is checked after each iteration. Always executes at least once.

/* Input validation - must execute at least once */
int number;
do {
    printf("Enter a positive number: ");
    scanf("%d", &number);
} while (number <= 0);

/* Menu system */
char choice;
do {
    printf("\\nMenu:\\n");
    printf("1. Option One\\n");
    printf("2. Option Two\\n");
    printf("q. Quit\\n");
    printf("Choice: ");
    scanf(" %c", &choice);

    switch (choice) {
        case '1': /* handle option 1 */ break;
        case '2': /* handle option 2 */ break;
    }
} while (choice != 'q');

for Loop

The most structured loop, combining initialization, condition, and update.

Standard form:

for (initialization; condition; update) {
    /* loop body */
}

Examples:

/* Traditional counting loop */
for (int i = 0; i < 10; i++) {
    printf("%d ", i);
}
/* Prints: 0 1 2 3 4 5 6 7 8 9 */

/* Multiple loop variables */
for (int i = 0, j = 10; i < j; i++, j--) {
    printf("i=%d, j=%d\\n", i, j);
}

/* Infinite loop with break */
for (;;) {  /* all three parts optional */
    printf("Press 'q' to quit: ");
    char c = getchar();
    if (c == 'q') break;
}

/* Working with arrays */
int array[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
    printf("array[%d] = %d\\n", i, array[i]);
}

Scope Note: In C99 and later, the loop variable can be declared in the initialization (as shown above). In older C standards, you must declare it outside:

int i;  /* pre-C99 style */
for (i = 0; i < 10; i++) {
    /* ... */
}

Jump Statements

break Statement

Exits the innermost switch statement or loop (for, while, do-while). Note that the break statement cannot break out of an if statement.

/* Breaking out of a loop */
for (int i = 0; i < 100; i++) {
    if (i * i > 500) {
        printf("Stopping at i=%d\\n", i);
        break;  /* Exit the for loop */
    }
}

/* Breaking from nested loops */
int found = 0;
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (i * j == 42) {
            printf("Found at i=%d, j=%d\\n", i, j);
            found = 1;
            break;  /* Only breaks inner loop! */
        }
    }
    if (found) break;  /* Need another break for outer loop */
}

Make sure that you know what you’re breaking out of when you break.

continue Statement

Skips the rest of the current loop iteration and proceeds to the next one.

/* Print odd numbers only */
for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;  /* Skip even numbers */
    }
    printf("%d ", i);
}
/* Prints: 1 3 5 7 9 */

/* Skip invalid input */
int sum = 0, value;
for (int i = 0; i < 5; i++) {
    printf("Enter positive number #%d: ", i+1);
    scanf("%d", &value);

    if (value <= 0) {
        printf("Invalid. Skipping.\\n");
        continue;  /* Skip to next iteration */
    }

    sum += value;
}

return Statement

Exits the current function and optionally returns a value to the caller.

/* Function with return value */
int max(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

/* Function returning void */
void print_hello(int times) {
    if (times <= 0) {
        return;  /* Early exit */
    }
    for (int i = 0; i < times; i++) {
        printf("Hello ");
    }
    printf("\\n");
    /* Implicit return at end of void function */
}

/* Multiple return points */
int safe_divide(int a, int b, int *result) {
    if (b == 0) {
        return -1;  /* Error code */
    }
    *result = a / b;
    return 0;       /* Success code */
}

goto Statement

Unconditionally jumps to a labeled statement within the same function.

/* Error handling with goto (cleanup pattern) */
int process_file(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (!file) {
        goto error;
    }

    char *buffer = malloc(1024);
    if (!buffer) {
        goto cleanup_file;
    }

    /* Process file... */

    free(buffer);
    fclose(file);
    return 0;  /* Success */

cleanup_file:
    fclose(file);
error:
    fprintf(stderr, "Error processing %s\\n", filename);
    return -1;  /* Failure */
}

/* Breaking from deeply nested loops */
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        for (int k = 0; k < 10; k++) {
            if (i + j + k == 30) {
                printf("Found at %d,%d,%d\\n", i, j, k);
                goto found_it;  /* Clean exit from all loops */
            }
        }
    }
}
found_it:
printf("Search complete.\\n");

Guidelines for goto:

  • Generally avoided in favor of structured control flow
  • Sometimes used for cleanup/error handling in C (as shown above)
  • Never jump over variable initializations

Best Practices

  1. Always use braces with control structures, even for single statements
  2. Indent consistently to show structure clearly
  3. Limit loop variable scope when possible (C99 for (int i = ...))
  4. Avoid complex conditions - use temporary boolean variables if needed
  5. Use default: in switch even if just to assert unexpected values
  6. Reserve goto for error cleanup only
  7. Keep functions small so return points are obvious
  8. Comment deliberate fallthrough in switch statements

Here is an example showing how statements can be put together to create logic in C :

#include <stdio.h>

int find_index(int arr[], int n, int key) {
    for (int i = 0; i < n; ++i) {
        if (arr[i] == key) {
            return i;          /* return from function */
        }
    }
    return -1;                 /* not found */
}

int main(void) {
    int v[] = {3, 1, 4, 1, 5};
    int idx = find_index(v, 5, 4);
    if (idx >= 0) {
        printf("found at %d\n", idx);
    } else {
        puts("not found");
    }
}

/ Continue

Follow the technical trail.

Use the dense notes as the source material, then move through the guided route, writing, or project proof when you want a cleaner entry point.