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

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

Текущая наиболее часто используемая версия языка, c99, принесла много новых возможностей, многие из которых совершенно неизвестны большинству программистов на C (в более старых спецификациях, очевидно, тоже есть свои темные уголки).

Вот те, о которых я знаю:

Sizeof может иметь побочные эффекты

int main(void) {
    return sizeof(int[printf("ooops\n")]);
}

sizeof на переменных типах требует исполнения произвольного кода.

Шестнадцатеричный float с экспонентой

int main() {
  return assert(0xap-1 == 5.0);
}

p означает степень, и за ним следует знаковая экспонента, закодированная по основанию 10. Выражение имеет тип double, но его можно изменить на float, добавив к литералу символ f.

Совместимые объявления и массивы как параметры функций

#include <stdio.h>

void a(); // 1
void a(long story, int a[*], int b[static 12][*][*]); // 2
void a(long story, int a[42], int b[*][*][64]);       // 3
void a(long story, int a[*], int b[const 42][24][*]); // 4
// void a(long story, int a[*], int b[*][666][*]);    // 5
// void a(long story, int a[*], int b[*][*][666]);    // 6

void a(long story, int a[42], int b[restrict 0 * story + a[0]][24][64]) {
    printf("%zu\n", sizeof(a));
    printf("%zu\n", sizeof(b));
}

int main() {
    a(0, 0, 0);
    return 0;
}

Здесь происходит много чего:

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

  • Если на момент объявления размер какого-либо массива неизвестен, то вместо него можно написать [].

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

  • Компилятор должен использовать новые объявления для заполнения недостающей информации о прототипе функции. Вот почему раскомментирование любого из объявлений 5 и 6 должно вызвать ошибку: 666 не является известным размером измерения массива. CLang игнорирует это. На самом деле, похоже, что объединение деклараций его совершенно не волнует.

  • Размер первого измерения не имеет значения, поэтому компилятор его игнорирует. Вот почему объявления 2 и 4 не конфликтуют, хотя их первое измерение имеет разный размер.

Древовидные структуры во время компиляции

struct bin_tree {
    int value;
    struct bin_tree *left;
    struct bin_tree *right;
};

#define NODE(V, L, R) &(struct bin_tree){V, L, R}

const struct bin_tree *tree = \
    NODE(4,
         NODE(2, NULL, NULL),
         NODE(7,
              NODE(5, NULL, NULL),
              NULL));

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

VLA typedef

int main() {
    int size = 42;
    typedef int what[size];
    what the_fuck;
    printf("%zu\n", sizeof(the_fuck));
}

Это является стандартом с C99. Понятия не имею, как это вообще может быть полезно.

Array designators

struct {
    int a[3], b;
} w[] = {
    [0].a = {
        [1] = 2
    },
    [0].a[0] = 1,
};

int main() {
    printf("%d\n", w[0].a[0]);
    printf("%d\n", w[0].a[1]);
}

С помощью данной фичи можно итеративно определить член структуры.

Препроцессор — функциональный язык

#define OPERATORS_CALL(X)  \
    X(negate, 20, !)       \
    X(different, 70, !=)   \
    X(mod, 30, %)

struct operator {
    int priority;
    const char *value;
};

#define DECLARE_OP(Name, Prio, Op)       \
    struct operator operator_##Name = {  \
        .priority = Prio,                \
        .value = #Op,                    \
    };

OPERATORS_CALL(DECLARE_OP)

Макрос можно передать в качестве параметра другому макросу.

Оператор switch можно мешать с другим кодом

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

int main(int argc, char *argv[]) {
    if (argc != 2)
        errx(1, "Usage: %s DESTINATION", argv[0]);

    int destination = atoi(argv[1]);

    int i = 0;
    switch (destination) {
        for (; i < 2; i++) {
        case 0: puts("0");
        case 1: puts("1");
        case 2: puts("2");
        case 3: puts("3");
        case 4: puts("4");
        default:;
        }
    }
    return 0;
}

Такие применения известны как устройства Даффа. Помимо прочего, они позволяют легко разворачивать цикл вручную.

Typedef — почти класс хранения

typedef работает почти так же, как inline или static.

Вы можете написать

void typedef name;

a[b] — синтаксический сахар

Знаю, ничего такого безумного. Но, тем не менее, это забавно!

a[b] буквально эквивалентно (a + b). Таким образом, можно написать абсолютное безумие, например 41[yourarray + 1].

Вызовы макросов в #include

Это валидный препроцессор:

#define ARCH x86
#define ARCH_SPECIFIC(file) <ARCH/file>
#include ARCH_SPECIFIC(test.h)

Несуразные объявления указателей

int (*b);
int (*b)(int);
int (*b)[5];   // 1
int *b[5];     // 2

Все это — допустимые декларации.

Скобки полезны для разграничения:

  • Объявление 1 — указатель на массив из 5 int

  • Объявление 2 — массив из 5 указателей на int

Одиночный # является допустимым препроцессором

Он ничего не делает.

#
#
#

int main() {
    return 0;
}

Это все, что я нашел!

Большую часть вышеперечисленного я нашел, читая спецификацию, а часть — читая продакшн код.

Желаю вам счастливых приключений на С :)

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


Как встроить экспертную систему в программу на С? Поговорим об этом на открытом уроке 3 июля. Мы обсудим, что такое экспертная система, когда она используется и на чем создается; а таже рассмотрим язык разработки экспертных систем и библиотеку CLIPS. Этот урок будет особенно полезен для разработчиков различных встраиваемых систем, например, подсистем умного дома, роботизированных систем.

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


  1. benjik
    28.06.2023 14:51
    +30

    Альтернативное название статьи: "За что бить по рукам на Code Review"


    1. arteast
      28.06.2023 14:51
      +10

      Ну почему же? Сompound literals, array designators и отдельные препроцессорные извращения (в частности, описанное в статье - вариация широко известного паттерна X-macro) убийственно полезны в отдельных случаях, сокращая копипасту и улучшая читабельность кода.


    1. maeris
      28.06.2023 14:51
      +1

      Да камон, 10 лет назад и не такое рассказывали. А тут-то что, тут даже почти не страшно.


    1. akuzm83
      28.06.2023 14:51

      "Методы повышения частоты суицидов среди начинающих (и не только) разработчиков."


  1. nomorewar
    28.06.2023 14:51
    +4

    Интересно, зачем все это в языке? Выглядит как: «смотри как могу!».


    1. Ritan
      28.06.2023 14:51
      +1

      Не зачем, а почему. Цели делать шаблоны с++ полными по Тьюрингу тоже не стояло


    1. event1
      28.06.2023 14:51

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


  1. artemisia_borealis
    28.06.2023 14:51
    +3

    Пожалуй, пустая директива — самое полезное здесь (или хотя бы безвредное), остальное — это для олимпиад по программированию или для жёского WO-стиля программирования


    А так конечно ещё...
    Триграф Эквивалентный символ
       ??=  #
        ??/ \
        ??' ^
        ??( [
        ??) ]
        ??! |
        ??< {
        ??> }
        ??- ~

    В Си их ещё не отменили....


    1. Viknet
      28.06.2023 14:51
      +3

      В драфте C23 триграфы убирают из стандарта как отслужившие своё.


  1. FD4A
    28.06.2023 14:51
    +1

    Си лучший, навсегда в моём сердечке. И немного полезного в работе, как правило коллеги не знают, а в статье не увидел (а оно там есть эх): X-MACROS. Великолепная штука, особенно для написания API, правда когда он начинает быть вложенным один в другой становиться жутковато, но для плоских структур самое то.


  1. Jianke
    28.06.2023 14:51

    Можно ли чуток поподробнее про устройство Даффа?


    1. Sazonov
      28.06.2023 14:51
      +1

      Вроде достаточно доступно на Википедии расписано


  1. truebest
    28.06.2023 14:51

    //практичное
    int k;
    sizeof k;


  1. HepoH
    28.06.2023 14:51
    +4

    a[b] буквально эквивалентно (a + b). Таким образом, можно написать абсолютное безумие, например 41[yourarray + 1].

    Как будто бы здесь ошибка и a[b] === *(a+b). т.е. обращение к элементу массива есть разыменование суммы начала массива с его индексом.

    А 41[yourarray+1] будет эквивалентно не сумме 41+yourarrray+1 (читай "смещению от начала массива на 42"), а разыменованию этой суммы (обращению к 42ому элементу массива).


  1. KongEnGe
    28.06.2023 14:51

    Ваши программисты должны быть осьминогами, чтобы отстрелить себе напрочь все ноги хотя бы частью приведенных способов.


  1. event1
    28.06.2023 14:51

    Слава т.н. богу, что я про это никогда не знал и узнал из статьи на хабре! Устройство Даффа — отрыв башки. Какая цепочка рассуждений может привести здорового человека к такой конструкции?!


    1. cupraer
      28.06.2023 14:51

      Цепочка рассуждений (она, в принципе, описана в Вики) «1 миллион итераций лучше, чем 8 миллионов, но что делать с обработкой хвоста?».


  1. kodimatrix
    28.06.2023 14:51

    На хабре есть отличная стать о VLA: клик.


  1. Pppnnn
    28.06.2023 14:51

    Макросы передавать как аргумент в другой макрос - это не такая уж малоизвестная возможность. А макросы внутри других макросов так вообще частое явление.


  1. KarmaCraft
    28.06.2023 14:51

    Статья очень полезная и дает почувствовать, что ты еще не мастодонт из 90 со знанием седого Си)) Реально, фичи интересные и некоторые можно даже применять. Однако, не могу въехать зачем пихать массивы в аргументы функции? Может я не правильно понял саму идею и от этого есть практическая польза?