Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

2025-03-14

Youtube-запись лекции от 2025-03-14: https://youtu.be/XxHtIcfYNqM

Роль побитовых операций в программах на C

30 = 16 + 8 + 4 + 2 = 00011110

  • Бинарную систему диктует электричество — но не строго
  • Много где хитро наприменяли

Вопрос данных, а не вычислений

Чаще всего двоичное число — это не число «в лоб», а что-то другое

А при чём тут драйверы, наша цель и мечта?

flowchart RL

subgraph Устройство
life([жизнь без цифр])
micro(микроконтроллер)
end

proto((протокол))

subgraph Компьютер
driver{{драйвер}}
digital([цифровой мир])
end

life:::analogue -.-o micro:::physical
micro <==> proto:::digit

proto <==> driver:::digit
driver o-.-o digital:::digit

classDef analogue fill:#880088,color:#fff;
classDef physical fill:#fff,stroke:#979736,stroke-width:5px,color:#880088;
classDef digit fill:#979736,color:#fff;
Loading

Цифровой мир очень абстрагировался от ноликов и единичек

А остальные — не очень

База побитовых операций в C

a & b

a | b

a ^ b

~a

a << n

a >> n

Какие абстракции можно собрать из нулей и единичек?

Побитовые операции происходят не то чтобы с битами

Числа

Ну все же знакомы с бинарной арифметикой?

  • Двоичный вывод числа
#include <stdio.h>

void print_binary(int x) {
    for (int i = 7; i >= 0; i--) {
        printf("%d", (x >> i) & 1);
    }
    printf("\n");
}

int main() {
    int a = 5, b = 3;
    printf("a = "); print_binary(a);
    printf("b = "); print_binary(b);
    printf("a & b = "); print_binary(a & b);
}
  • Обмен значений без временной переменной
int a = 5, b = 7;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d, b = %d\n", a, b);
  • Является ли число степенью двойки?
#include <stdio.h>
int is_power_of_two(int n) {
    return (n > 0) && ((n & (n - 1)) == 0);
}

Флаги

#define FLAG_ENABLE  (1 << 0)  // 00000001
#define FLAG_ERROR   (1 << 1)  // 00000010

uint8_t flags = 0;
flags |= FLAG_ENABLE;  // Установить бит
flags &= ~FLAG_ERROR;  // Сбросить бит
  • Установить конкретный бит
int x = 5;  // 00000101
x = x | (1 << 3);  // Устанавливаем бит 3
printf("%d\n", x);  // 13 (00001101)
  • Проверить, установлен ли конкретный бит — &
int x = 42;  // 00101010
int mask = 0x08;  // 00001000 или (1 << 3)
if (x & mask) {
    printf("Бит 3 установлен!\n");
}
  • Инвертировать конкретный бит — ^
int x = 5;  // 00000101
x = x ^ (1 << 2);  // Переключаем бит 2
printf("%d\n", x);  // 1 (00000001)
  • Сбросить конкретный бит — & ~
int x = 13;  // 00001101
x = x & ~(1 << 3);  // Обнуляем бит 3
printf("%d\n", x);  // 5 (00000101)

Маски

  • Применить маску — &
  • Проверить установленность — сравнить с маской
int flags = 0b10101010;
int mask = 0b00001100;

if ((flags & mask) == mask) {
    printf("Оба бита установлены!\n");
}
  • Объединить флаги в маску — и потом проверять
#define READ  (1 << 0)  // 0001
#define WRITE (1 << 1)  // 0010
#define EXEC  (1 << 2)  // 0100

int permissions = READ | EXEC;  // 0001 | 0100 = 0101

if (permissions & READ) printf("Можно читать\n");
if (permissions & WRITE) printf("Можно писать\n");
if (permissions & EXEC) printf("Можно исполнять\n");
  • Конкретно для сетей
#include <stdio.h>

int main() {
    int ip = 0b11000000101010000000000100000010;  // 192.168.1.2
    int netmask = 0b11111111111111111111111100000000;  // /24 (255.255.255.0)
    int network = ip & netmask;

    printf("Сетевой адрес: %08X\n", network);
}

Кодирование

Интуитивно понятно, когда есть документация

Битовые поля

  • Сразу говорим, сколько бит берём под поле
struct Flags {
    uint8_t ready : 1;
    uint8_t error : 1;
    uint8_t : 5;
    uint8_t active : 1;
};
  • На разных процессорах разное поведение
  • Не все процессоры и умеют-то
  • Нельзя сделать указатель на отдельное поле внутри структуры
  • Феномены: выравнивание, big/little endian
  • Чтобы работать с полем как единым объектом, нужен union (ну или просто массив внутри структуры)
#include <stdio.h>
#include <stdint.h>

union Byte {
    struct {
        uint8_t bit0 : 1;
        uint8_t bit1 : 1;
        uint8_t bit2 : 1;
        uint8_t bit3 : 1;
        uint8_t bit4 : 1;
        uint8_t bit5 : 1;
        uint8_t bit6 : 1;
        uint8_t bit7 : 1;
    } bits;
    uint8_t byte;  // Полный байт
};

int main() {
    union Byte b;
    b.byte = 0xAA;  // 10101010

    printf("Бит 0: %d\n", b.bits.bit0);
    printf("Бит 1: %d\n", b.bits.bit1);
    printf("Целый байт: 0x%X\n", b.byte);

    return 0;
}
  • Логические операции с полем как единым объектом — ну можно, конечно: *(uint8_t*)&field = 0xFF — но некомфортно

Union

  • Оборачиваем битовое поле
#include <stdio.h>

union Status {
    struct {
        unsigned int ready  : 1;
        unsigned int error  : 1;
        unsigned int active : 1;
        unsigned int mode   : 2;
        unsigned int unused : 3;
    };
    unsigned char value;  // Полный байт
};

int main() {
    union Status status = { .value = 0 };

    status.ready = 1;
    status.error = 0;
    status.active = 1;
    status.mode = 2;

    printf("Статус: 0x%X\n", status.value);
}

Протоколы

  • Когда что какой бит значит (часто в байте)
  • I2C, SPI, UART, USB,…

Регистры

  • Ячейки памяти внутри процессора/микроконтроллера
  • Ещё быстрей оперативной
  • Управляют в т. ч. периферией (да много чем)
  • Установил, сбросил, прочитал
#define GPIO_REG  *(volatile uint32_t*)0x3FF44004

void set_pin_high() {
    GPIO_REG |= (1 << 5);  // 5-й бит в 1
}

…и многое другое

  • битовая упаковка/разделение #FF0000
  • контрольная сумма
  • побитовая адресация
  • логические шины