- PVSM.RU - https://www.pvsm.ru -
Я ловлю в далёком отголоске,
Что случится на моём веку.
(«Гамлет», Борис Пастернак)
Признаться, пишу на чистом C я не так уж и часто и за развитием языка уже давно не слежу. Но тут произошло два неожиданных события: С вернул [1] себе звание популярнейшего языка программирования по версии TIOBE и случился анонс первой за долгие годы действительно интересной книги [2], посвящённой этому языку. Поэтому я провёл несколько вечеров за изучением материалов о C2x — следующей версии C.
Самыми, на мой взгляд, интересными нововведениями я и хочу поделиться с читателями Хабра.
Уверен, большая часть читателей в курсе, как происходит развитие C, но на всякий случай объясню терминологию и расскажу историю языка в двух словах.
В 1989 году популярнейший язык программирования C перешёл на новую ступень развития — стал американским национальным (ANSI [3]) и международным (ISO [4]) стандартом. Эту версию C негласно назвали C89, или ANSI C — в противовес многочисленным диалектам, существовавшим до этого.
Новая редакция стандарта языка выходит примерно раз в десять лет, на текущий момент существует четыре редакции: оригинальный C89, C99, C11, C18. Год публикации следующей версии неизвестен, поэтому документ в работе называют C2x.
Вносит изменения в стандарт специальная рабочая группа — WG14, в которую входят заинтересованные представители индустрии из разных стран. В англоязычной профильной литературе эту группу часто называют “Committee”, а я для её обозначения буду использовать слово «комитет».
Комитет получает в работу от участников предложения, каждому из которых присваивается обозначение вроде N2353 [5]. Предложения обычно включают в себя мотивацию внесения изменений, ссылки на другие документы, конкретные изменения стандарта. Предложения могут иметь несколько редакций, каждая из которых получает уникальное обозначение.
Комитет может, к примеру, проголосовать за обсуждение другой редакции предложения, учитывающей замечания, внесение предложенных изменений в стандарт, отказ во внесении изменений и так далее. У каждого предложения может быть один из следующих статусов: не обсуждалось, отправлено на доработку (и, вероятно, будет принято), принято как есть, отклонено.
Эту статью я разбил на три части. Очерёдность установил по степени вероятности внесения изменений в стандарт. В первой части я перечислю те предложения, которые уже принял комитет. Во второй оказались предложения, которые были восприняты положительно, но возвращены авторам на доработку. В последней же части я собрал самое «вкусное» — слухи о неопубликованных предложениях, обсуждаемых «в кулуарах» членами комитета.
Я, вероятно, покажусь невеждой, если скажу, что не знал об отсутствии этих функций в стандартной библиотеке C. Казалось бы, что может быть очевиднее и проще копирования строк? Но нет, C не такой, это вам не POSIX.
Итак, с опозданием лет на 20 к нам приходят [5] функции strdup и strndup!
#include <string.h>
char *strdup (const char *s);
char *strndup (const char *s, size_t size);
Приятно осознавать, что комитет иногда всё же принимает неизбежное.
У разработчиков больших компиляторов C есть любимая игра: придумывать собственные расширения к языку в форме атрибутов объявлений и определений. Сам язык, конечно же, специального синтаксиса для таких вещей не предоставляет, поэтому каждый изощряется как может.
Чтобы хоть как-то привести этот балаган к порядку без создания десятков новых ключевых слов, комитет придумал синтаксис-который-будет-управлять-всем. Словом, в следующей версии стандарта принимается синтаксис [6] для указания атрибутов. Пример из предложения:
[[attr1]] struct [[attr2]] S { } [[attr3]] s1 [[attr4]], s2 [[attr5]];
Здесь attr1
относится к s1
и s2
, attr2
относится к определению struct S
, attr3
— к типу struct s1
, attr4
— к идентификатору s1
, attr5
— к идентификатору s2
.
За включение атрибутов в стандарт комитет уже проголосовал, но до публикации обновлённой версии стандарта ещё далеко. Поэтому авторы предложений пользуются новой игрушкой. Некоторые из предложенных атрибутов:
Атрибут deprecated
позволяет пометить объявление как устаревшее. При использовании таких объявлений компиляторам предлагается предупреждать программиста.
Атрибутом fallthrough
можно явно пометить места в ветвлениях switch, где поток исполнения должен попасть в следующую ветвь тоже.
С помощью атрибута nodiscard
можно явно указать необходимость обработки возвращаемого функцией значения.
Если переменная или функция осознанно не используется, можно пометить её атрибутом maybe_unused
(а не почти уже идиоматичным (void) unused_var
).
Атрибутом noreturn
можно пометить функцию, которая не вернётся в место вызова.
Устаревший ещё в 1989 году такой способ объявления параметров функции, как «объявление в стиле K&R» (он же — «когда типы после скобочек указываются», он же — «я не понимаю старый код на C»), будет, наконец, сожжён на костре [7], а я смогу расслабиться и не следить за своими void-ами.
Другими словами, больше нельзя будет делать так:
long maxl (a, b)
long a, b;
{
return a > b ? a: b;
}
Эпоха Просвещения приходит в код на C! Объявления функций будут делать именно то, что приличные люди от них ожидают:
/* объявление функции без аргументов */
int no_args();
/* тоже объявление функции без аргументов */
int no_args(void);
Бесконечная, казалось бы, история близка к завершению. Комитет смирился [8] с тем, что единорогов и сказочных архитектур не существует, а программисты на C имеют дело с дополнительным кодом [9] (англ. two's complement) для представления знаковых целых чисел.
В текущем виде это уточнение немного упростит стандарт, но в перспективе поможет (!) избавиться от «любимого» программистами неопределённого поведения языка — переполнения знакового целого числа.
Если перечисленные выше изменения уже, можно сказать, существуют в нашей реальности, то следующая группа предложений пока находится в разработке. Тем не менее комитет предварительно их одобрил и при должном усердии авторов они точно будут приняты.
Я стабильно пишу одну-две пробные программы на C в неделю. И, признаться, мне уже давно надоело указывать имена неиспользованных аргументов main
.
Реализация одного [10] из положительно оценённых комитетом предложений позволит не указывать лишний раз имена параметров в определениях функций:
int main(int, char *[])
{
/* И никакой перхоти! */
return 0;
}
Мелочь, но какая приятная!
После долгого, до-о-олгого переходного периода комитет, наконец, решил больше не придуриваться [11] и принять [12] в язык, эм, «новые» ключевые слова: true
, false
, alignas
, alignof
, bool
, static_assert
и другие. Заголовки вроде <stdbool.h>
можно будет, наконец, почистить.
Включение двоичных данных из файлов в исполняемый файл — невероятно полезная для всех игроделов возможность [13] :
const int music[] = {
#embed int "music.wav"
};
Надеюсь, члены комитета понимают, что Хабрасообществу известно место проведения следующего заседания, и эта директива препроцессора будет принята без вопросов.
Кажется, на смену проблемному макросу NULL
приходит ключевое слово nullptr
, которое будет эквивалентно выражению ((void*)0)
и при приведении типов должно оставаться типом-указателем. Любое использование NULL
предлагается сопровождать предупреждением компилятора:
/* Вы же никогда не пишете просто NULL? Я вот до сих пор затылок чещу. */
int execl(path, arg1, arg2, (char *) NULL);
/* Но счастье близко */
int execl(path, arg1, arg2, nullptr);
Если пример вам ни о чём не говорит, то загляните в документацию для Linux по адресу man 3 exec
, там будет пояснение.
Обработка ошибок функций стандартной библиотеки — давняя проблема C. Сочетание неудачных решений в ранних версиях стандарта, консервативности комитета и вопросов обратной совместимости не позволяло найти устраивающее всех решение.
И вот наконец нашёлся герой, готовый предложить [14] решение одновременно разработчикам компиляторов, сверхконсервативному комитету и нам, простым смертным:
[[ oob_return_errno ]] int myabs (int x) {
if(x == INT_MIN ) {
oob_return_errno ( ERANGE , INT_MIN ) ;
}
return (x < 0) ? -x : x;
}
Обратите внимание на атрибут oob_return_errno
. Он означает, что из этой функции-шаблона будут сгенерированы следующие функции:
Возвращающая структуру с флагом ошибки и результатом работы функции (struct {T return_value; int exception_code}
).
Возвращающая результат работы функции и игнорирующая возможные ошибки в аргументах, приводя к неопределённому поведению.
Завершающая выполнение в случае ошибки в аргументах.
Заменяющая errno
, то есть обладающая привычным поведением.
Компилятору предлагается выбирать между этими вариантами в зависимости от того, как использует функцию программист:
bool flag;
int result = oob_capture(&flag , myabs , input) ;
if (flag) {
abort ();
Здесь корректность выполнения функции показывается флагом flag
, причём errno
не затрагивается. Аналогично выглядят, например, вызовы функций с сохранением кода ошибки в переменную.
Конкретный синтаксис, похоже, ещё будет меняться, но хорошо то, что комитет хотя бы думает в этом направлении.
Автор «Effective C» совместно с другими членами комитета отвечали [15] на вопросы участников англоязычного сообщества Hacker News. Вопросов и ответов по ссылке много, многое пересекается с отмеченными выше вещами. Но есть пара важных для программистов пунктов, которые не оформлены как предложения, но члены комитета намекают на то, что какая-то работа в этих направлениях уже ведётся.
Ключевое слово typeof
уже давно реализовано [16] в компиляторах и позволяет не повторяться при написании кода. Канонический пример:
#define max(a,b)
({ typeof (a) _a = (a);
typeof (b) _b = (b);
_a > _b ? _a : _b; })
Мартин Себор (Martin Sebor), ведущий разработчик из Red Hat и участник Комитета, утверждает, что соответствующее предложение уже находится в работе и почти наверняка будет одобрено.
Держу пальцы скрещенными.
Некоторые языки программирования, в том числе и реализованные на базе Clang и GCC, позволяют привязывать высвобождение ресурсов к лексическим областям видимости переменных или, проще говоря, вызывать какой-то код с выходом программы из области видимости переменной.
В чистом C нет и никогда не было такой возможности, но компиляторы уже давно реализуют атрибут cleanup(<cleanup function>)
:
int main(void)
{
__attribute__((cleanup(free))) char *s = malloc(sizeof(*s));
return 0;}
Роберт Сикорд (англ. Robert Seacord), автор «Effective C» и член комитета, признался [17], что работает над предложением в стиле ключевого слова defer
из Go:
int do_something(void) {
FILE *file1, *file2;
object_t *obj;
file1 = fopen("a_file", "w");
if (file1 == NULL) {
return -1;
}
defer(fclose, file1);
file2 = fopen("another_file", "w");
if (file2 == NULL) {
return -1;
}
defer(fclose, file2);
/* ... */
return 0;
}
В приведённом примере функция fclose будет вызвана с аргументами file1
и file2
при любом выходе из тела функции do_something
.
Близится революция!
Изменения в C — как мутации в генетике: происходят редко, частенько бывают нежизнеспособны, но в итоге способствуют эволюции.
Последние неудачные изменения в C случились десять лет назад. А последний качественный скачок в разработке на языке произошёл более 20 лет назад. И, судя по всему, в новой итерации стандарта члены комитета решили всё-таки подумать над поступательным движением вперёд.
В общем, пользуйтесь статическими анализаторами, почаще запускайте Valgrind и старайтесь не писать слишком больших программ на C!
Автор: Владимир Казанов
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/353233
Ссылки в тексте:
[1] вернул: https://www.tiobe.com/tiobe-index/
[2] интересной книги: https://nostarch.com/Effective_C
[3] ANSI: https://en.wikipedia.org/wiki/American_National_Standards_Institute
[4] ISO: https://en.wikipedia.org/wiki/International_Organization_for_Standardization
[5] N2353: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2353.htm
[6] синтаксис: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2335.pdf
[7] сожжён на костре: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2432.pdf
[8] смирился: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf
[9] дополнительным кодом: https://ru.wikipedia.org/wiki/%D0%94%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4
[10] одного: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2510.pdf
[11] придуриваться: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2457.pdf
[12] принять: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2458.pdf
[13] возможность: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2499.pdf
[14] предложить: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2429.pdf
[15] отвечали: https://news.ycombinator.com/item?id=22865357
[16] реализовано: https://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof
[17] признался: https://news.ycombinator.com/item?id=22866311
[18] Источник: https://habr.com/ru/post/503140/?utm_source=habrahabr&utm_medium=rss&utm_campaign=503140
Нажмите здесь для печати.