Привет, Хабр! Недавно я задумался: Python — не единственный инструмент, которым я хочу оперировать в своих инструментах. Python, понятно, легко освоить и он применяется везде, но язык-то не идеальный! Ресурсов требует много, да и время выполнения не ахти, а учитывая нынешние темные времена... Мне нужно что-то получше. В общем, тут я вздумал попробовать Си.

Как Си спас инфраструктуру человечества и сделает это еще раз

Стоит отметить, что процесс написания кода на Си пускай и трудоемок, и надо следить за огромным количеством факторов, но этот язык является золотым стандартом производительности, который может переплюнуть разве что ассемблер в руках мастера, чей стаж уходит в десятилетия.

Родился этот язык в 1972 году, когда о таких вещах, как веб-разработка, и речи не шло, а игры тогда были вообще без кода, исключительно аппаратные системы! (Например, игра Pong для компьютера Atari 1972 года)

Спасибо автору книги "Python для детей и родителей" Брайсону Пэйну за экспозицию
Спасибо автору книги "Python для детей и родителей" Брайсону Пэйну за экспозицию

После своего появления Си стал началом новой эпохи разработки программного обеспечения: пока ассемблер был близок к машинному коду, а Бейсик тогда был неким аналогом сегодняшнего Python, Си знатно задрал лапку под древом разработки инфраструктуры.

Влияние Си на ассемблер:

  • Ассемблер не был поглощен Си, но потребность в нем заметно снизилась, а его использование резко сократилось. До этого именно на нем писались многие системные программы (и части ОС тоже);

  • Когда Си предложил все то же самое, но лучше, он начал использоваться разве что там, где Си не справлялся: обработка аппаратных прерываний, работа с привилегированными инструкциями процессора, оптимизация критических мест, и так далее;

  • Стоит также отметить, что большинство компиляторов Си, такие как GCC или Clang, поддерживают ассемблер, вшитый в код Си. Пример синтаксиса GCC: asm("movl %eax, %ebx");

Влияние Си на Бейсик:

  • Бейсик был подвержен влиянию Си куда сильнее. Ранние версии Бейсика (особенно интерпретируемые) подвергались критике из-за ряда проблем: обилие GOTO (спагетти-код передает привет), слабую модульность, ограниченные возможности работы с данными...

  • Под влиянием Си Бейсик преобразился (стал снова великим). В нем появились процедуры и функции, как в Си, блочная структура кода (BEGIN/END, SUB/FUNCTION), локальные переменные и структуры данных (как struct в Си);

  • Большинство версий Бейсика начали мигрировать в компилируемый формат (например, QuickBASIC). Код начал оптимизироваться на уровне компилятора, а еще появилась возможность создавать исполняемые программы (.exe-файлы);

  • Бейсик даже начал потакать Си: в некоторых версиях (FreeBASIC, например) он начал поддерживать интеграцию .h-файлов (заголовочные файлы Си) и компиляцию кода в тот же формат, что и Си.

Время лихое, но для умелых золотое

Перенесемся в современность, в которой даже в той же веб-разработке преимущественно сидят Python (FastAPI) и JS — интерпретируемые ЯП, разработка на которых легкая, но при этом инференс довольно ресурсозатратен.

В данный момент, принимая во внимание суровые реалии, только слепоглухонемая бабка из глубин Сибири не слышала о всемирном кризисе ОЗУ, виной которому наш любимый (нет) нейрослоп, который мы каждый день видим в любом соцсети, даже порой на этом же Хабре.

Использование генеративного ИИ пошло не туда: он проложил красную ковровую дорожку лентяям, которые вооружились Sora и пошли брать штурмом видеохостинги. Нагрузка на мощности огромная, даже пользовательская DDR5 идет нарасхват, а облачные сервисы дорожают.

Ради этого мы пожертвовали компьютерами
Ради этого мы пожертвовали компьютерами

Да и жесткие диски тоже на низком старте (хранить нейрослоп данные для обучения ИИ, тоже надо). Дал бы руку на отсечение, что дальше в расход пойдет вода (для охлаждения систем)... Но не буду этого делать, от греха подальше.

Понятное дело, что это никуда не годится. Все уже массово оптимизируют свой код. Я еще не знаю, как именно это происходит, но я бы в этой ситуации вынес горячие зоны бэкенда в компилируемые языки. Си — один из них. Пусть я и не уверен, что он вытеснит Go и Rust, но в критических точках он самый производительный.

На нем построено множество современных ЯП (Python, C++, Rust). В сотни раз быстрее Python и поедает во столько же раз меньше ресурсов. Любимец микроконтроллеров, ОС и прочих специфичных разновидностей ПО. Си — спаситель человечества.

Взято с https://m.vk.com/yaprogrammer?from=groups
Взято с https://m.vk.com/yaprogrammer?from=groups

Мой личный опыт с Си

Найдя на Степике бесплатный курс по Си и скачав C××droid из Google Play (уровень подготовки — бог), я сразу же побежал писать следующий код:

int main()
{
  printf("Hello, world!")
}

...и сразу же с порога получил ошибку. Даже три сразу.

  • Во-первых, я забыл заголовок #include <stdio.h>. Она содержит самые базовые функции ввода и вывода;

  • Во-вторых, после каждой строки кода должна быть точка с запятой (";"), мол, "начало мысли" -> "конец мысли". Как в JS, но тут это обязательно;

  • В-третьих, желательно в функции main() в конце приписывать return 0;. Ошибка более стилистическая для новых компиляторов, но если этого не сделать, то в некоторых случаях вы даже не будете знать, когда программа завершится. Возврат нуля здесь является чем-то вроде выражения "уйти с миром".

После радушных объятий компилятора и анализа своих ошибок мой код выглядел так:

#include <stdio.h>

int main()
{
  printf("Hello, world!");
  return 0;
}

На Python для этого мне бы понадобилась одна строка! Зато вместо миллисекунд Python (конкретно для этого случая) у меня на вывод ушло, ну... Гораздо меньше времени. Вычитая компиляцию в бинарник.

После часа изучения курса по Си я осмелел и даже написал что-то в духе:

#include <stdio.h>

int main()
{
	int x = 7;
	int y = 2;
	printf("%f", (float)x / (float)y); // вывод: 3.500000
	return 0;
}

Код объявляет две целочисленных переменных: x (7) и y (2). А затем делит их между собой (до этого переведя их в формат типа 7.0 и 2.0) и получает 3.500000 на выводе.

Небольшое пояснение: в Си при делении целых чисел 7 / 2 получится 3. Деление двух целых чисел работает здесь как оператор // в Python. Если вы хотите получить конкретный результат, то переводите числа в float или double.

Возмужавши от своих подвигов на курсе, я решил слегка выйти за его рамки и написать что-то свое. Например, те же "камень-ножницы-бумагу". Особенно после того, как я узнал, что в stdlib.h есть функция rand(), которая, впрочем, работает чуть иначе, чем в Python. Но об этом осознании чуть попозже.

Веселая нарезка (страданий) моего мозга

Прототип на Python выглядел бы примерно так:

import random
plays = ["rock", "paper", "scissors"]

# Обработка ввода игрока
p_choice = input()

while p_choice != "q": # выход при вводе q
  player = player.lower() # перевод всего ввода в нижний регистр
  c_choice = random.choice(plays) # выбор компьютера

  # Логика сравнения
  if p_choice == c_choice:
    print("Draw!")
  elif p_choice == "rock":
    if c_choice == "scissors":
      print("You won!")
    else:
      print("Computer won!")
  elif p_choice == "scissors":
    if c_choice == "paper":
      print("You won!")
    else:
      print("Computer won!")
  elif p_choice == "paper":
    if c_choice == "rock":
      print("You won!")
    else:
      print("Computer won!")
  
  print() # пропустить строку
  p_choice = input()

На Си это оказалось гораздо сложнее. Мне пришлось столкнуться с:

  • указателями строк;

  • сегфолтами;

  • определением функций (и я не про int main);

  • массивами;

  • условиями;

  • и прочими буками Си

Начнем с того, что при попытке объявить массив я написал:

char arr[3] = {"rock", "paper", "scissors"};

...НЕТ. То есмь массив из 3 символов, а я пытался запихнуть туда строки. Надо делать либо:

char *arr[3] = {"rock", "paper", "scissors"}; 
// массив указателей

Либо:

char arr[3][10] = {"rock", "paper", "scissors"}; 
// двумерный массив из 3 строк по 10 символов
// этот способ мне не понравился, так что я взял первый

Потом я принялся реализовывать рандом. Избалованный Python, я попробовал функцию rand() из stdlib.h:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	char *plays[3] = {"rock", "paper", "scissors"};
	int count = sizeof(plays) / sizeof(plays[0]);
	int index = rand() % count;
	printf("%s", plays[index]);
	return 0;
}

Вот только чего-то не хватает. Почему-то мне всегда выпала только "бумага": я не задал семя рандома (srand). Для исправления сего недоразумения мне пришлось сделать так:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>  // нужно для time()

int main() {

    srand(time(NULL));  // устанавливаем яблоко рандома

    char *plays[3] = {"rock", "paper", "scissors"};
    int count = sizeof(plays) / sizeof(plays[0]);
    int index = rand() % count;
    printf("%s\n", plays[index]);
    return 0;
}

Теперь рандом зависит от того времени, когда программа выполняется!.. И все же можно сделать лучше.

#include <unistd.h>

unsigned int seed = time(NULL) ^ getpid();
srand(seed); // устанавливаем яблоко рандома (но лучше!)

Выглядит это так:

  • мы получаем текущее время на момент запуска;

  • а также PID операции (все PIDы разные в рамках одной операции ОС);

  • побитовое XOR (^) комбинирует оба источника энтропии, усиливая перемешку;

Теперь наш генератор работает горвздо лучше!

Позже, когда я добрался до написания условий, я решил посмотреть, как сравнивать строки в Си, и не зря. Тут не прокатит прямое сравнение, как в Python: в Си для этого существует функция strcmp(str1, str2), возвращающая число, равное:

  • 0, если они равны;

  • -1, если первый ASCII-символ первой строки меньше второго;

  • 1, если наоборот

Прозвучало страшно, но я быстро понял, как это использовать. Я создал условие вида if (strcmp(p_choice, "scissors") == 0), которое определит, ввел ли игрок "scissors". Сам же алгоритм определения выглядит так:

#include <stdlib.h>
#include <string.h>

int main()
{
		if (strcmp(c_choice, p_choice) == 0) 
		{
			printf("\nDraw!\n");
		}
	
		if (strcmp(c_choice, "rock") == 0 && strcmp(p_choice, "paper") == 0 || strcmp(c_choice, "scissors") == 0 && strcmp(p_choice, "rock") == 0 || strcmp(c_choice, "paper") == 0 && strcmp(p_choice, "scissors") == 0) // условия победы игрока
		{
			printf("\nPlayer won!\n");
		}

		if (strcmp(c_choice, "paper") == 0 && strcmp(p_choice, "rock") == 0 || strcmp(c_choice, "scissors") == 0 && strcmp(p_choice, "paper") == 0 || strcmp(c_choice, "rock") == 0 && strcmp(p_choice, "scissors") == 0) // условия победы компьютера
		{
			printf("\nComputer won!\n");
		}
}

Не рекомендую вникать в код: из-за своих ограниченных технических способностей в синтаксисе Си я решил не рисковать, за что поплатился качеством человеческого восприятия кода. Если бы эту статью писал ИИ, он бы определенно стал размышлять на тему лишения человечности во имя алгоритмов машин, но я не буду этим заниматься.

После этого я столкнулся с еще одной напастью: почему-то if игнорировал мой ввод! Я не знал, как это произошло (я все же вводил "paper", я искренне не понимаю, как это случилось), но на всякий случай я решил определить функцию, переводящую ввод в нижний регистр.

Видите ли, у Си нет прямого аналога питоновского .lower(). Есть только tolower() из ctype.h, но он охватывает только один символ, а потому мне пришлось объявить первую Си-функцию, которая НЕ является main():

#include <ctype.h>

// перевод в нижний регистр
void lower(char *str) {
	for (int i = 0; str[i]; i++) {
		str[i] = tolower(str[i]);
	}

Я буду честен: я больше скопировал эту функцию, чем написал ее. Все же я вник, и понял, как она работает:

  • void — наша функция ничего не возвращает (int перед main() означает, что main() вернет число);

  • char *str — указатель на строку;

  • for здесь, в принципе, остался в том же амплуа, что и в Python, разве что замаксировался иначе;

  • int i = 0 — в тандеме с for и i++ образует выражение вида питоновского for i in range;

  • str[i] — как в Python, берет индекс буквы в строке;

  • str[i] = tolower(str[i]) — меняет символ на этот же символ нижнего регистра (в Python строки неизменяемые, но в Си вполне)

И в логике ввода:

int main()
{
    printf("Enter your choice:\t");
		scanf("%s", p_choice);
		lower(p_choice); // теперь наш ввод в нижнем регистре
		printf("Computer picked %s", c_choice);
		printf("\nYour input: %s", p_choice);
}

Неизвестно почему, но опосля определения функции все заработало.

Кстати, о вводе:

char *p_choice = "...";
printf("Введите выбор: ");
scanf("%s", &p_choice);

Никогда так не делайте. Тут дорога только в сегфолт (Segmentation fault).

  • p_choice — это указатель на строковый литерал "...". Они хранятся в памяти для чтения, их нельзя менять;

  • &p_choice передавал адрес указателя, а не адрес буфера для строки;

  • Введенная строка летит в память чтения — поздравляем, вы выиграли "Segmentation fault"!

Лично я решил проблему, изменив секцию на оную типа:

char p_choice[100];  // буфер для ввода в 100 символов
printf("Введите выбор:\t");
scanf("%s", p_choice);  // без &, потому что массив уже адрес

Вконец уставший от сложностей Си, я почти закончил алгоритм. Все, что мне осталось — это сделать цикл while, потому что неудобно запускать программу сначала каждый раз. Но с этим проблем уже не возникло:

while (strcmp("q", p_choice) != 0)
{
  // ...логика игры...
  // ...тут же и логика ввода пользователя в конце...
}

if (strcmp("q", p_choice) == 0) //окончание игры
{
	printf("\nQuitting...\n");
}

После тестирования я выдохнул с облегчением. Наконец, я закончил с логикой этой игры, полностью переведя ее с Python на Си. И если в Python мне бы понадобилось 25 строк, то на Си мне понадобилось 45 строк. Большинство из них — фигурные скобки на отдельной строке, да и код кое-где можно было бы оптимизировать, но эта задача пока не для меня.

Моя реализация Си-кода выглядит так:

// #include <stdio.h> в принципе, можно исключить во имя stdlib.h
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>

// перевод в нижний регистр
void lower(char *str) {
	for (int i = 0; str[i]; i++) {
		str[i] = tolower(str[i]);
	}
}

int main()
{
	unsigned int seed = time(NULL) ^ getpid();
	srand(seed); // устанавливаем яблоко рандома
	
	char *plays[3] = {"rock", "scissors", "paper"};
	int count = sizeof(plays) / sizeof(plays[0]);
	int index = rand() % count;
	char *c_choice = plays[index]; // рандомный выбор
	
	char p_choice[100];
	while (strcmp("q", p_choice) != 0)
	{
		printf("Enter your choice:\t");
		scanf("%s", p_choice);
		lower(p_choice);
		printf("Computer picked %s", c_choice);
		printf("\nYour input: %s", p_choice);
	
		if (strcmp(c_choice, p_choice) == 0) 
		{
			printf("\nDraw!\n");
		}
	
		if (strcmp(c_choice, "rock") == 0 && strcmp(p_choice, "paper") == 0 || strcmp(c_choice, "scissors") == 0 && strcmp(p_choice, "rock") == 0 || strcmp(c_choice, "paper") == 0 && strcmp(p_choice, "scissors") == 0) // условия победы игрока
		{
			printf("\nPlayer won!\n");
		}

		if (strcmp(c_choice, "paper") == 0 && strcmp(p_choice, "rock") == 0 || strcmp(c_choice, "scissors") == 0 && strcmp(p_choice, "paper") == 0 || strcmp(c_choice, "rock") == 0 && strcmp(p_choice, "scissors") == 0) // условия победы компьютера
		{
			printf("\nComputer won!\n");
		}
	}
	// окончание игры
	if (strcmp("q", p_choice) == 0)
	{
		printf("\nQuitting...\n");
	}
	return 0;
}

Говорите, что хотите, но я горжусь этим кодом. Написал я его на второй день изучения Си (возможно, мне не стоило заскакивать вперед курса и искать решения багов окаяных), но в конечном итоге у меня получилось.

Взято с https://m.vk.com/itumor?from=groups
Взято с https://m.vk.com/itumor?from=groups

В конце можно замолвить словечко о том, что учить Си пусть и будет нелегко, но зато код будет быстрым и эффективным. Мне понравилось то, что в этом языке все надо настраивать самому, а это уже по-настоящему максимальный контроль. printf() и scanf(), как ни странно, тоже показались мне гораздо дружественнее (никогда такого не было, чтобы я ошибся в указателях с ними. Хоть убейте — не было!). Пусть функции с массивами позже и заставили меня взвыть, но в целом Си мне понравился.

Ожидаю фидбека в комментариях!

Комментарии (70)


  1. remindscope
    23.02.2026 09:56

    Позабавил тэг "разработка игр"

    Автор, вот вы когда сами пишете этот хаос из if'ов, у вас ничего не щелкает в голове?


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Убрал


  1. Apoheliy
    23.02.2026 09:56

    Возможно, немного не в тему, но:

    А какой фидбек вы ожидаете?

    То, что вы попробовали Си - это классно. В любом случае это новый взгляд на программирование и управление ресурсами.

    То, что в программе есть что доделать - это тоже очевидно:

    scanf("%s", p_choice); -> scanf("%99s", p_choice);

    Результат scanf лучше контролировать, т.к. если вы введёте один пробел, то p_choice рискует остаться неизменным;

    p_choice на старте лучше обнулить/очистить. Так-то она мусором заполнена, и если это мусор соответствует букве q, то программа сразу же выйдет;

    т.к. в программе нет отработки "мусорного" ввода (на запрос ввели "123"), то можно сделать немного полегче-получше: пользователь что-то вводит и в цикле по трём строкам идёт сравнение: какой из вариантов он ввёл? Если ни одному не соответствует, то просим повторить. А если вариант найден, то дальше идёт сравнение чисел. ЧИСЕЛ, КАРЛ! Лучше сравнивать числа: 0 тупит 1, 1 разрезает 2, 2 обёртывает 0.

    // окончание игры

    if (strcmp("q", p_choice) == 0) {

    printf("\nQuitting...\n");

    }

    можно заменить на просто printf ... - в этом месте p_choice и так равен "q".

    Прим.: в нативных языках Си, C++ строковый тип считается "не простым" типом данных (в общем, то это так и есть для всех языков, просто это стараются скрыть в тумане). И поэтому от строк стараются "отойти": если можно переключиться на числа - то лучше работать с числами.


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Благодарю за критику. Обязательно приму во внимание Ваши советы.


    1. Sap_ru
      23.02.2026 09:56

      Там вообще можно сравнение в два условия запихать если нумерацию перевернуть: 0 побеждает 2, а остальные тупо по увеличению значения..


      1. Garantia_Tsverga Автор
        23.02.2026 09:56

        А ведь вы правы. Надо бы мне побольше мыслить числами


  1. kmatveev
    23.02.2026 09:56

    В C если тело оператора if состоит из одного оператора, то фигурные скобки можно не писать, на 8 строк меньше.

    Логика вашей игры на C и на питоне отличается: в C вы формируете случайный выбор компьютера вне игрового цикла, так что будете угадывать один и тот же вариант.

    Плюсанул хотя бы даже за то, что статья написана живым человеком.


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Меня уже поставили в известие. Насколько я помню, из-за моего просчета (я не заключил "яблоко рандома" в цикле while) выбор выполняется только один раз, так что выбор тоже будет идти только однажды. Благодарю за комментарий.


      1. kmatveev
        23.02.2026 09:56

        Не совсем. Вызов srand() нужно оставить вне цикла, а вот вызов rand() внести вовнутрь.


      1. Deosis
        23.02.2026 09:56

        Откуда вообще вы взяли термин яблоко рандома?


        1. Garantia_Tsverga Автор
          23.02.2026 09:56

          От яблони раздора укатилось


    1. AbuMohammed
      23.02.2026 09:56

      Для людей экономящих фигурные скобки после ифа в аду предусмотрен отдельный котел. Имхо).


      1. AHL
        23.02.2026 09:56

        Привет из котла


    1. VM1989
      23.02.2026 09:56

      А вот MISRA C:2012 правило 15.6 рекомендует ставить фигурные скобки во всех случаях. Потому что сегодня там одна строка, завтра две. Да и читаемость улучшается


      1. Garantia_Tsverga Автор
        23.02.2026 09:56

        Вы технически правы, но немного не по адресу. Правила MISRA появились из-за критических багов в коде, которые убивали людей. В основном они идут в промышленной разработке, но в мелких хобби-проектах ими можно пренебречь.


  1. Octagon77
    23.02.2026 09:56

    Ожидаю фидбека в комментариях!

    Напрашиваетесь, сударь, напрашиваетесь...

    Вы там, в Питоне своём, как сыр в масле катаясь расслабились до наивности детской. Хотите приложение под Андроид - Kivy, хотите фронтенд - да хоть Anvil.works, и так со всем - Питон и ещё совсем чуть-чуть дают результат. А на С - стоит выйти за вот такую ножницы-бумагу... Андроид приложение - извольте, SDK, NDK, Native Activity и куча подробностей, с которыми можно разобраться, но изложены они с точки зрения Котлина, а то и Джавы. Фронтенд - а Вам какой, на Emscripten или Clang/LLVM? Ну и кусок JavaScript в придачу.,

    То, что Вы увидели на ножницах с бумагой - это парадный фасад, а на деле может быть так, что пока Вы, пытаясь получить реально полезный результат, будете учить С, а на это неделя по вечерам уже много, и всю обвязку что и есть задница, можно будет выучить весь Rust и получить тот же результат от Cargo, потом выучить Go и получить результат ещё раз от Go (замечаете - и того проще чем с Растом), а остаток времени провести в душевных метаниях - может быть надо было сразу отдаться, ну или примкнуть к, Flutter?

    С Flutter, кстати, возможен пример. На Python вы берёте Flet и ездите на Flutter фрирайдером. На C - сначала Dart, потом Flutter, потом Dart FFI и только потом вопрос "а ради чего?". Если кто-то поклялся Вам хорошо платить за C, ну знает или думает что знает ради чего, это одно дело, а если нет...

    Так что от меня фидбек такой - бросайте эту ерунду, вы знакомитесь с С так, как это делали тогда, когда альтернатив не было. Вы питонист? Отлично, напишите модуль на С. Убедитесь, что он и на Андроид, с тем же Kivy например, тоже работает. Напишите что-нибудь на SDL или Qt. И только после этого у Вас появится право решать - понравился С или нет.


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Разве C не учат перед C++? В моем видении C — только промежуточная ступень, чтобы получше разобраться в тех же C++/Rust. Хотя, по такой же логике, перед C стоило бы посмотреть на Go/CSharp.


      1. tenzink
        23.02.2026 09:56

        Если вам интересен C++ или Rust, то для этого совершенно необязательно учить C. В случае с C++ многое из того, что вы освоите в C будет доступно, но категорически не рекомендовано к использованию. Нет ничего плохого в изучении C, если он интересен сам по себе, но врядли полезно, как подготовку к изучению того же C++


        1. Garantia_Tsverga Автор
          23.02.2026 09:56

          Тогда я просто начал бы писать на C++, как на Python. Тогда геморроя было бы в 10 раз больше. Я предпочту сначала понять, как работают системы с минимумом абстракций, чтобы уже потом плавно перетечь в C++.


          1. tenzink
            23.02.2026 09:56

            Ну, писать на C++ как на python у вас всё равно не получилось бы, как не получилось на C. И не стоит ожидать, что C можно плавно "перетечь" в C++. Зная C, можно легко начать писать работающие программы, которые будут компилироваться C++ компилятором. Но для идиоматичного кода придётся учиться заново, местами переучиваясь от привычек из C


      1. kmatveev
        23.02.2026 09:56

        Можно знать C и не знать ничего больше, его достаточно для многих применений.


        1. Garantia_Tsverga Автор
          23.02.2026 09:56

          Если очень постараться, то можно и API для бэкенда написать на C.

          Я сегодня ради эксперимента взял ИИ (я все еще не умею писать на C, могу только вникать в код) и провел бенчмарк FastAPI-обертки + бэкенда на C против уже существующей этой же обертки на TS. Суть программы была в том, что это была админ-панель (пользователи могли регистрироваться, а админы могли их банить).

          Я построил архитектуру так, что Python асинхронно сгребает в батчи (10, 50, 100, 200) запросы и посылает их в C-бэкенд. С количеством обрабатываемых батчей росла и скорость обработки (98× скорости при батче в 200 запросов, и оно шло только вверх по метрике!)

          На моем динозавре 2009 года с 6 ГБ ОЗУ оно летало. Я завтра попробую уйти от PostgreSQL и посмотреть в сторону RocksDB, там должно быть еще больше. C++ тоже мне интересен, но я пока не буду его трогать, пусть и знаю несколько прикольных фреймворков для обработки данных


      1. Octagon77
        23.02.2026 09:56

        Разве C не учат перед C++?

        Наверняка есть и такие случаи, когда учат. Иначе вот такое вот без надобности было бы.

        Sic!
        Sic!

        C и C++ языки очень разные. То, что они местами внешне похожи - следствие того, что на момент создания плюсов поголовно все заинтересованные лица знали C. Далее сходство сохраняли как дань традиции, но сами ответственные лица подчёркивают - это ни в какой степени не являлось целью и делалось строго по возможности.

        Изучать C ради лучшего последующего изучения плюсов - ошибка. Чтобы C просто не мешал, его нужно не только основательно изучить, но и к нему привыкнуть, чтобы языки не смешивались один с другим. Если хочется и C и C++, тогда хорошо, если только плюсы - двойная работа в лучшем случае.

        Ниже Вы пишете

        Тогда я просто начал бы писать на C++, как на Python.

        и это очень странно. С одной стороны, как писать как на Пайтон если на плюсах половина кода в одном файле, а другая половина - в другом? С другой стороны, на Пайтон можно писать в любом стиле, в том числе как на плюсах с макросами.

        Если языки сами по себе, как хобби или предмет любования логическими, да и алогичными тоже, построениями, не интересуют - никакого смысла в полиглотстве нету. А рассказы типа "я выучил ... и стал лучше ..." - это скорее от нежелания признавать собственные ошибки, чем от искренности.

        Например, заинтересовала тема функционального программирования (спецом подальше от C). Тогда можно прочитать про функциональное программирование и подумать как это будет на своём Пайтоне. Где именно прочитать, я не знаю, однако. Если Вы тоже не знаете, ну не удалось выяснить, то берёте книгу по функциональному языку, скажем по Лисп, и читаете по диагонали. Далее то же самое - уложить идеи на Пайтон. Ключевое слово - по диагонали, то есть никак не выучить.

        Применительно к C или плюсам - посмотрите на Ассемблер и, если не в курсе то обязательно, на архитектуру машины, особенно на систему прерываний.


        1. Garantia_Tsverga Автор
          23.02.2026 09:56

          А что учить легче — C или плюсы? Не по отделению "по какому языку материалов больше", а скорее по именно усвоению материала


          1. Apoheliy
            23.02.2026 09:56

            На мой скромный взгляд, чистый Си учить намного легче. Вот совсем (и намного) легче. На C++ одни пляски с шаблонами чего стоят!

            НО: если рассматривать условно "большие" проекты, то более понятный, чистый, корректный код будет скорее всего на C++ (конечно, если вы на нём умеете). Просто потому, что стандартных средств для типовых задач - их намного больше. И контроль типов лучше. Повторюсь: это всё работает, если вы умеете "в плюсы". Если вы будете на C++ писать как на чистом Си (многие так и делают и это работает) - тогда преимуществ не будет.


            1. Garantia_Tsverga Автор
              23.02.2026 09:56

              Получается, что на C++ в сложности обучения идет кривая до небес, а после этого плато?


              1. Apoheliy
                23.02.2026 09:56

                Попробую дать другую аналогию (скорость + дистанция) (также: сравнение с Питоном сделано для контраста, не нужно сильно придираться):

                == Задаём скорость:

                Питон - это как передвижение по просёлочной дороге (утоптано, умято, вполне ровно);

                Си - это передвижение по перепаханному полю (вроде не проваливаемся по пояс, но если побежим, то можно и ноги повредить);

                C++ - это передвижение по утрамбованному полю (двигаемся медленно, но возможность повредить ноги снижается).

                == Включаем дистанцию:

                Начинающий разработчик - ну что там говорить - это быстро везде, особенно с ИванИванычем;

                Продвинутый разработчик:

                на Си - добраться до деревни в 5 км на север;

                на Питоне - это обойти 10 полян на севере на расстоянии 10 км;

                на C++ - это побывать во всех полянах на севере на расстоянии 10 км.

                ---

                А теперь переходим к исходному вопросу: что легче? По-моему, легче прийти в одну известную недалёкую точку, хоть и по перепаханному полю.

                Очевидно, у Вас возможны другие приоритеты. Банально, разный темперамент может повлиять: кто-то не готов "медленно, в одном направлении, а деревни всё ещё не видно".

                ---

                Прим.: по-моему, кривая обучаемости здесь несколько неинформативна, т.к. объём навыков (ордината графика) сильно разная - количество полян отличается очень-очень. Соответственно и сравнивать крутизну некорректно.

                Прим.Прим.: В C++ нет как таковой сложности обучения до небес (например, если что-то не знаете - то вообще босса не замочить ничего сделать нельзя): если что-то слишком сложно, то вы такими средствами не пользуетесь (или используете готовые библиотеки) - и всё ок.


                1. Garantia_Tsverga Автор
                  23.02.2026 09:56

                  Так красиво расписано все... В поэты бы Вам.


                1. Siemargl
                  23.02.2026 09:56

                  Прим.Прим.: В C++ нет как таковой сложности обучения до небес (например, если что-то не знаете - то вообще босса не замочить ничего сделать нельзя): если что-то слишком сложно, то вы такими средствами не пользуетесь (или используете готовые библиотеки) - и всё ок.

                  Босс: ты же знаешь С++, у нас на новой платформе проект не собирается, что то с бустом, поправь...


              1. Siemargl
                23.02.2026 09:56

                Нет, потом следующая кривая до небес. Первая была еще пологой =)


          1. Deosis
            23.02.2026 09:56

            Сам язык С максимально простой, но при этом и за программиста он ничего не делает. Поэтому за всеми подводными камнями нужно следить самостоятельно.
            С++ может взять на себя рутину, но надо знать что написать.


  1. Serpentine
    23.02.2026 09:56

    void lower(char *str) {
    	for (int i = 0; str[i]; i++) {
    		str[i] = tolower(str[i]);
    }

    А теперь используем:

    ...
    char just_char_array[] = {'e', 'n', 'd', 'l', 'e', 's', 's', ' ', 'F', 'u', 'n'};
    lower(just_char_array);
    ...

    Наслаждаемся UB.


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Напомню две вещи:

      • Этот код я написал на второй день своего изучения C, причем не обошлось без интенсивного поиска информации по абстрактной логике, не говоря уже о багах и сегфолтах;

      • Код функции предназначен для строк, а не для массивов со строками

      Я ценю и уважаю Ваш профессионализм, но надо же вникать в контекст ситуации.


      1. Serpentine
        23.02.2026 09:56

        Я же по этому поводу:

        Ожидаю фидбека в комментариях!

        Ничего страшного, научитесь еще.

        • Код функции предназначен для строк, а не для массивов со строками

        И про этот "маленький" нюанс узнаете в процессе.


        1. Garantia_Tsverga Автор
          23.02.2026 09:56

          Прозвучало как страшное предзнаменование. Спасибо за предупреждение.


          1. Serpentine
            23.02.2026 09:56

            Указатели, массивы и строки, их взаимосвязь и то, как они передаются (или возвращаются) в функции — очень интересные темы и не такие уж сложные, как может показаться с первого взгляда. Вам должно понравиться.


            1. Garantia_Tsverga Автор
              23.02.2026 09:56

              Это и вправду так. scanf() дался мне очень легко, да и строки вида printf("Значение: %d", num) я полюбил даже больше питоновского форматирования строк. Я сам в предвкушении будущей работы с ними.


              1. Ambroyz
                23.02.2026 09:56

                С "дружелюбным" scanf() Вас тоже ждёт сюрприз. Если Вы свою текущую программу на си будете вызывать на беке с передачей туда пользовательского ввода, то дадите remote code execuition.

                Дело в том, что scanf не проверяет размер целевого буфера, т.к. физически не может это делать не зная его размер. В итоге он проедется по стеку, делая типичный buffer overflow. В умелых руках этого достаточно для атаки.


                1. Garantia_Tsverga Автор
                  23.02.2026 09:56

                  scanf("%99s", &p_choice) //ограничивает до 99 символов


      1. posternack
        23.02.2026 09:56

        Начните с Столярова, иначе сищность головного мозга не избежна


    1. IlyasA74
      23.02.2026 09:56

      В его программе все char* заканчиваются нулями (из-за инициализации списком, надо же, сделали такое в C), поэтому UB не будет.

      Поправка: scanf формирует null-terminated последовательность char, поэтому в случае p_choice UB тоже не будет.


    1. ValeryIvanov
      23.02.2026 09:56

      Хорошо было бы предложить решение, которое бы позволило в данном случае избежать UB. Вопрос только в том, существует ли оно.


      1. oeditus
        23.02.2026 09:56

        Просто решение — разумеется существует, выделите буфер подлиннее, скопируйте туда аргумент, добавьте в конец \0 и работайте с этой безопасной копией. Но так никто не делает, конечно.


        1. ValeryIvanov
          23.02.2026 09:56

          Я просто не понял, что @Serpentine предлагает сделать, чтобы избежать UB непосредственно в функции lower. Какой бы тип не был выбран для строки(указатель, указатель+длина), всё равно пользователь функции сможет передать испорченные данные.


    1. IlyasA74
      23.02.2026 09:56

      Последовательность char just_char_array[] должна обязательно заканчиваться нулем '\0', иначе это некорректная строка C.


      1. Sap_ru
        23.02.2026 09:56

        А это не строка. Это массив, вообще-то. Сами-то посмотрите, что у вас объявлено - массив char.


        1. IlyasA74
          23.02.2026 09:56

          Все верно, массив char содержит NUL-terminated строку C, т.е. последовательность символов, которая заканчивается символом NUL (\0).


          1. 0x1b6e6
            23.02.2026 09:56

            Ваше утверждение было бы верно, если бы там был строковый литерал, но его там нет.

            char arr1[] = "abc";         ///< вот тут есть null в конце
            char arr2[] = {'a', 'b', 'c'}; ///< а вот тут нет
            


  1. Asterris
    23.02.2026 09:56

    Омг, ну ты бы ещё на ассемблере пытался это написать - да ещё и изучая язык по ходу.

    Я, зная и Си и ассемблер и ещё много других классических языков, точно также буду плеваться от Питона, который не знаю вообще.


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Ассемблер пока отложим — кто знает, что будет через месяц


  1. oeditus
    23.02.2026 09:56

    В 2026 так на питоне тоже писать не нужно. Объявите пару enum’ов и вперед:

        match (player_move, computer_move):
            # Draw cases
            case (Move.ROCK, Move.ROCK) | (Move.SCISSORS, Move.SCISSORS) | (Move.PAPER, Move.PAPER):
                return GameResult.DRAW
            
            # Player wins
            case (Move.ROCK, Move.SCISSORS) | (Move.SCISSORS, Move.PAPER) | (Move.PAPER, Move.ROCK):
                return GameResult.WIN
            
            # Computer wins
            case (Move.ROCK, Move.PAPER) | (Move.SCISSORS, Move.ROCK) | (Move.PAPER, Move.SCISSORS):
                return GameResult.LOSE
            
            case _:
                return GameResult.INVALID_INPUT


    1. Garantia_Tsverga Автор
      23.02.2026 09:56

      Можно полный код такой версии?