C/C++ (именно в таком сочетании) я в базовом варианте изучил за пару летних месяцев перед универом. Тогда для меня он был просто заменой паскалю, и уровень задач был соответствующий — всякая мелочевка для развлечения и разнообразные числодробилки. Универ со своими лабами не сильно что изменил (я получил автомат по программированию в те времена, когда по всем предметам надо было сдавать экзамены и не было балльной системы); Python, JavaScript и даже Java мимо пробегали, но всякие тесты булевой функции на монотонность проще писались на C/C++.

На первых двух работах мне даже платили за то, что я писал на C++, но я тогда был джуном и это все сейчас кажется несерьезным (хотя уже будучи зрелым специалистом написал на C++ модуль ядра для BSD, это был прикольный, но мимолетный опыт). Тем более что потом переключился на Python, оттуда на Scala и понеслось…

В общем, получается, что всерьез, профессионально, на Си я и не писал никогда. А вот в последние несколько месяцев я как раз этим и занимался в рамках научного проекта. И (вполне закономерно) оказалось, что процесс не сильно-то отличается от других языков.

Все стандартные паттерны в наличии, например:

  • разделение интерфейса (.h) и реализации (.c)
  • своего рода полиморфизм можно построить на структурах и указателях на функции
  • разделение api и impementation в зависимостях (где включать заголовок — в .c или .h)
  • всякие билдеры, стратегии и фасады — это вообще легко
  • typedef — one love ❤️
  • даже шаблоны можно сделать, правда макросами
  • и т.п.

При этом возникает понимание многих конструкций, которые казались “лишними”: extern, static, макросы (увы, некоторые прикольные штуки только ими и получается делать), префиксы для функций из разных модулей (даже в не очень большом проекте словил коллизию имен, неймспейсов не хватает), #ifdef DEBUG и отдельная сборка под valgrind или санитайзеры (потому что без отладочного -g особо и не отладишь утечки памяти, а еще valgrind не знает всех инструкций из -march=native и может даже врать про номера строк на -03). Более того, -Wall выдает все замечания по делу! Хотя inline с его приколами все еще невнятный какой-то :/

В более высокоуровневых языках обычно многие вещи делаются гораздо проще (но не во всех конечно *выразительно смотрит на java*), да и “мыслишь” после них более абстрактно. Часто себя ловил на мысли, что вот тут лямбду надо бы, а их особо и нет (только указатели на функции), а вот тут хотелось бы иметь возможность тип менять (вместо void *), а вот тут частичное применение функции прям зашло бы… Не хватает простых вещей типа Option, а указатели уже не хочешь использовать, потому что дешевле структуру передать.

Увы, инструменты разработки — не сильная сторона Си. Makefile еще можно потерпеть, autoconf сотоварищи — просто жесть, пакетный менеджер — мимо, VS Code опять выбесил по какой-то фигне, а добил меня миллион настроек clang-format, после которых я “обманываю” форматтер пустым комментарием, чтобы не совсем отвратно выдавал список аргументов функции. Впрочем, ничего нового (осторожно, по ссылке кринж). После космических технологий вроде IntelliJ Idea или Gradle — все очень грустно.

При этом язык все еще развивается. Например, весьма пригодились составные литералы:

return (some_struct_t){
    .field1 = value1, 
    .field2 = .value2, // trailing comma FTW!
} 

Сейчас есть стандарт c17, а еще грядет c23 — и там есть много прикольных штук, про многие из которых я могу сказать: да, такая фича пригодилась бы! Даже лямбды маячат на далеком горизонте, но добавить их — непростая задача.

В общем, писать что-то на низкоуровневом языке достаточно интересно (если это не Zig :)). Это полезное упражнение, чтобы понять, как много делают всякие хорошие инструменты и библиотеки, да и собственный прогресс оценить. “Вернуться к истокам” тоже прикольно. Когда писал пост, откопал в папке со своей универской фигней вот такую хрень, которая датируется 2010 годом:

#include <stdio.h>
class foo
{
friend bool operator < ( bool left, const foo& right );
friend int operator ^ ( int left, const foo& right );
};

bool operator < ( bool left, const foo& right ) { return left; }
int operator ^ ( int left, const foo& right ) { return left; }

int main()
{
int O_o = 0, _ = 0, baka = 0; foo o_O, neko;

bool XD = O_o >_< o_O;
int nya = baka ^_^ neko;

//printf("%d ",nya);
printf("%d ",XD);
return 0;
}

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

Что бы там не пророчили, кажется, что Си пока рано умирать.