Если у вас несколько лет опыта программирования на языке 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)
nomorewar
28.06.2023 14:51+4Интересно, зачем все это в языке? Выглядит как: «смотри как могу!».
Ritan
28.06.2023 14:51+1Не зачем, а почему. Цели делать шаблоны с++ полными по Тьюрингу тоже не стояло
event1
28.06.2023 14:51подозреваю, что большая часть — неожиданные побочные эффекты, либо приемлемые побочные эффекты патчей для дыр в предыдущих версиях спецификации
artemisia_borealis
28.06.2023 14:51+3Пожалуй, пустая директива — самое полезное здесь (или хотя бы безвредное), остальное — это для олимпиад по программированию или для жёского WO-стиля программирования
А так конечно ещё...Триграф Эквивалентный символ ??= # ??/ \ ??' ^ ??( [ ??) ] ??! | ??< { ??> } ??- ~
В Си их ещё не отменили....
FD4A
28.06.2023 14:51+1Си лучший, навсегда в моём сердечке. И немного полезного в работе, как правило коллеги не знают, а в статье не увидел (а оно там есть эх): X-MACROS. Великолепная штука, особенно для написания API, правда когда он начинает быть вложенным один в другой становиться жутковато, но для плоских структур самое то.
HepoH
28.06.2023 14:51+4a[b] буквально эквивалентно (a + b). Таким образом, можно написать абсолютное безумие, например 41[yourarray + 1].
Как будто бы здесь ошибка и
a[b] === *(a+b)
. т.е. обращение к элементу массива есть разыменование суммы начала массива с его индексом.А
41[yourarray+1]
будет эквивалентно не сумме41+yourarrray+1
(читай "смещению от начала массива на 42"), а разыменованию этой суммы (обращению к 42ому элементу массива).
KongEnGe
28.06.2023 14:51Ваши программисты должны быть осьминогами, чтобы отстрелить себе напрочь все ноги хотя бы частью приведенных способов.
event1
28.06.2023 14:51Слава т.н. богу, что я про это никогда не знал и узнал из статьи на хабре! Устройство Даффа — отрыв башки. Какая цепочка рассуждений может привести здорового человека к такой конструкции?!
Pppnnn
28.06.2023 14:51Макросы передавать как аргумент в другой макрос - это не такая уж малоизвестная возможность. А макросы внутри других макросов так вообще частое явление.
KarmaCraft
28.06.2023 14:51Статья очень полезная и дает почувствовать, что ты еще не мастодонт из 90 со знанием седого Си)) Реально, фичи интересные и некоторые можно даже применять. Однако, не могу въехать зачем пихать массивы в аргументы функции? Может я не правильно понял саму идею и от этого есть практическая польза?
benjik
Альтернативное название статьи: "За что бить по рукам на Code Review"
arteast
Ну почему же? Сompound literals, array designators и отдельные препроцессорные извращения (в частности, описанное в статье - вариация широко известного паттерна X-macro) убийственно полезны в отдельных случаях, сокращая копипасту и улучшая читабельность кода.
maeris
Да камон, 10 лет назад и не такое рассказывали. А тут-то что, тут даже почти не страшно.
akuzm83
"Методы повышения частоты суицидов среди начинающих (и не только) разработчиков."