// Undefined Behavior в C: примеры, которые ломают интуицию
// Компиляция без оптимизаций: cc -O0 -o ub_demo ub_demo.c
// Компиляция с оптимизациями: cc -O2 -o ub_demo_opt ub_demo.c
// Сравните вывод двух версий — он будет разным.

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

// 1. Знаковое переполнение: компилятор считает, что его не бывает
void signed_overflow_demo(void) {
    printf("--- 1. Знаковое переполнение ---\n");

    int x = INT_MAX;
    printf("x = INT_MAX = %d\n", x);
    printf("x + 1 = %d\n", x + 1);  // UB! Результат непредсказуем.

    // Компилятор имеет право предполагать, что x + 1 > x всегда true,
    // и может удалить проверки на переполнение.
    if (x + 1 > x) {
        printf("x + 1 > x — компилятор считает это ВСЕГДА истинным\n");
    } else {
        printf("x + 1 <= x — переполнение произошло (только без оптимизаций)\n");
    }
    printf("\n");
}

// 2. Удаление проверки на NULL
// При -O2 компилятор может удалить проверку, если указатель
// уже был разыменован до неё.
int check_after_deref(int *p) {
    int val = *p;       // разыменование — UB если p == NULL
    if (p == NULL) {    // компилятор: «p уже разыменован, значит не NULL»
        return -1;      // эта ветка может быть удалена при -O2
    }
    return val;
}

void null_check_demo(void) {
    printf("--- 2. Удаление проверки на NULL ---\n");
    printf("check_after_deref(NULL) — поведение зависит от -O уровня\n");
    printf("При -O0: вернёт -1 (проверка сработает)\n");
    printf("При -O2: компилятор может удалить проверку на NULL\n");
    printf("  (указатель уже разыменован — значит, он не NULL)\n\n");
}

// 3. Бесконечный цикл без побочных эффектов — UB в C11+
// Компилятор имеет право удалить его целиком.
void infinite_loop_demo(void) {
    printf("--- 3. Бесконечный цикл ---\n");
    printf("Цикл без побочных эффектов — UB в C11.\n");
    printf("Компилятор может удалить его или превратить в __builtin_unreachable().\n\n");
    // unsigned int i = 0;
    // while (i < 10) {} // если i не меняется — UB, цикл может быть удалён
}

// 4. Выход за границы массива
void buffer_overflow_demo(void) {
    printf("--- 4. Выход за границы массива ---\n");
    int arr[4] = {10, 20, 30, 40};
    printf("arr[0..3] = {%d, %d, %d, %d}\n", arr[0], arr[1], arr[2], arr[3]);
    printf("arr[4] — выход за границу, UB.\n");
    printf("В C нет проверки границ. Читаем мусор из памяти: arr[4] = %d\n", arr[4]);
    printf("arr[1000] — тоже скомпилируется без ошибок.\n\n");
}

// 5. Обнуление памяти, которое компилятор удаляет
void secure_erase_demo(void) {
    printf("--- 5. «Мёртвое» обнуление буфера ---\n");
    char password[32];
    strcpy(password, "s3cret_passw0rd!");
    printf("Пароль в буфере: %s\n", password);

    // Программист обнуляет буфер «для безопасности».
    // Но компилятор видит: после memset буфер не читается.
    // Значит, memset — «мёртвый код» и его можно удалить.
    memset(password, 0, sizeof(password));

    printf("После memset (при -O2 компилятор может удалить вызов!):\n");
    printf("  password[0] = 0x%02x\n", (unsigned char)password[0]);
    printf("Используйте explicit_bzero() или memset_s() вместо memset.\n\n");
}

int main(void) {
    printf("=== Undefined Behavior в C ===\n");
    printf("Скомпилируйте с -O0 и -O2, сравните результаты.\n\n");

    signed_overflow_demo();
    null_check_demo();
    infinite_loop_demo();
    buffer_overflow_demo();
    secure_erase_demo();

    printf("--- Итог ---\n");
    printf("Всё вышеперечисленное компилируется без предупреждений (cc -Wall).\n");
    printf("Поведение программы зависит от уровня оптимизации.\n");
    printf("Это и есть Undefined Behavior.\n");
    return 0;
}
