Малоизвестные возможности языка C

от автора

Если у вас несколько лет опыта программирования на языке 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. Этот урок будет особенно полезен для разработчиков различных встраиваемых систем, например, подсистем умного дома, роботизированных систем.


ссылка на оригинал статьи https://habr.com/ru/companies/otus/articles/744542/


Комментарии

Добавить комментарий

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