Короткий ответ: потому что мы так сказали.
:)
... Что?
Ладно, признаю, для статьи это неприемлемо короткий ответ, и мои провокационные слова требуют пояснений.
Изначально проведение конференции Комитета по С было запланировано во Фрайбурге (Германия). Но по некоторой причине она состоялась не там и завершилась в пятницу, 7 августа. Встреча прошла хорошо, мы добились значимого прогресса по всем направлениям. Мы делаем реальные успехи — я вас уверяю, что действительно делаем, и C — не мертвый язык.
Попутно замечу, что я также стал редактором проекта для C. Поэтому прежде, чем вы воспримете этот текст как тираду неосведомленного человека, слишком ленивого, чтобы заниматься «улучшением ситуации», позвольте мне вас заверить, что я действительно заинтересован в том, чтобы C начал удовлетворять потребности разработчиков, при этом не нуждаясь в 50 специфических расширениях для создания хоть сколько-нибудь удаленных хороших/полезных библиотек и приложений.
Тем не менее, я сделал утверждение, и я должен его обосновать. Мы могли бы рассмотреть тысячи CVE – список известных уязвимостей и дефектов безопасности) и проблем, свойственных коду на языке С, или необходимость для MISRA тщательно контролировать каждую маленькую потенциальную фичу языка С, чтобы предотвратить неправильное использование (привет, объявления прототипов по K&R...), или другие более сложные и забавные баги, связанные с переносимостью и неопределенным поведением. Но вместо этого мы просто рассмотрим информацию из уст самого Комитета.
О, время доставать попкорн?!
Нет, дорогой читатель, не торопись доставать попкорн. Как и во всех процедурах ISO, мне нельзя цитировать кого-либо дословно. И это не статья, в которой мы будем называть конкретные имена и заниматься шеймингом. Однако в ней мы постараемся объяснить, почему случаи, которые мы можем легко диагностировать и идентифицировать как плохое поведение, в стандартном С никогда не исчезнут. И начнем мы с документа доктора Филиппа Клауса Краузе (Dr. Philipp Klaus Krause): «N2526, использование const для неизменяемых данных из стандартной библиотеки».
N2526 — это очень простой документ. «Некоторые данные, возвращаемые библиотекой, являются const
морально, духовно и фактически даже по своей реализации. Это неопределенное поведение и писать в них — неправильно, так что давайте перестанем издеваться друг над другом по этому поводу». ... Ладно, там не совсем так написано, но я уверен, дорогой читатель, что идею вы поняли. Изначально, когда проводилось голосование за этот документ, голосов против почти не было. Затем несколько человек решительно выразили возражения, потому что он ломает старый код. Конечно, это плохо. Даже у меня перехватило дыхание, и я с напряжением подумал — добавить const
? У C нет ABI, на который это могло бы повлиять, C (его реализации) даже не учитывает квалификаторы, как мы можем что-то сломать?! Итак, давайте поговорим о том, почему в глазах некоторых людей это стало бы критическим изменением.
Язык С
Или, как мне нравится его называть, «язык “Типобезопасность — это для неудачников”». Конечно, это слишком громко сказано, поэтому придется обойтись просто "С". Возможно, вам интересно, почему я утверждаю, что у языка С нет типобезопасности. Если на то пошло,
struct Meow {
int a;
};
struct Bark {
double b;
void* c;
};
int main (int argc, char* argv[]) {
(void)argc;
(void)argv;
struct Meow cat;
struct Bark dog = cat;
// error: initializing 'struct Bark' with an expression of incompatible type 'struct Meow'
return 0;
}
Давайте честно: по мне, Джим, это похоже на строгую типизацию! Дальше, конечно, все становится только пикантнее:
#include <stdlib.h>
struct Meow {
int a;
};
struct Bark {
double b;
void* c;
};
int main (int argc, char* argv[]) {
(void)argc;
(void)argv;
struct Meow* p_cat = (struct Meow*)malloc(sizeof(struct Meow));
struct Bark* p_dog = p_cat;
// :3
return 0;
}
и̷͎̭̫͐̕б̸̮́͜о̷̝́̈́̀͜ ̵̺̳͍̀́̀я̴͇̳̊̉̑ ̵̜͍̂̒п̸̬̻̱̈́͋̀ё̶͍̄̓с̵̜̞͊-̶̳̗̊р̵̟̮̫͐͛̎а̶̘͈̎̍̚з̸͓͆͋͊р̷̳͉̘̇̃͝у̸͚͔̬̃͌͆ш̷͖̰͕̎ӥ̴͎́̇̊т̸̜͌̇е̷̢͕̞̃л̵̲̑ь̴̧͒̚ ̴̜̌̈́ͅв̶̱͔͓̐с̵̝͇̎̚ё̶̠̱̞̈́г̵̮̓͋͝о̵͖̳͌͂"
Да, два совершенно несвязанных типа указателей могут быть могут быть приравнены друг к другу в следующем стандарту C. Большинство компиляторов предупреждают об этом, но по стандарту этот код будет принят, если только вы не зададите опции -Werror
-Wall
-Wpedantic
и т.д. и т.п.
Также без явного преобразования компилятор может принять следующие вещи, связанные с указателями :
volatile
(кому вообще нужна эта семантика?!)const
(записывайте в любые данные, доступные только для чтения!)_Atomic
(потоковая безопасность, шмотоковая безопасность!)
Заметьте, я не утверждаю, что у вас вообще не должно быть возможности делать эти вещи. При работе с языком С легко получить функцию в 500 или 1000 строк с именами переменных, которые не описывают, для чего они предназначены. И Неопровержимый Факт™ заключается в том, что вы работаете в основном с указателями, и у вас нет никакой безопасности в той мере, в какой это касается основ языка. (Примечание: Это действительно нарушение ограничений, но есть так много legacy-кода, что каждая реализация игнорирует квалификаторы, так что код никогда не перестанет компилироваться из-за этого (спасибо, @fanf!)! Каждый потенциальный сбой здесь можно легко диагностировать с помощью компилятора, и все из них выдают предупреждения, но никогда не потребуют от вас преобразования типа, чтобы сообщить компилятору, что вы действительно имели это в виду. Что гораздо важнее, это также означает, что люди, которые придут после вас, не будут иметь ни малейшего представления о том, действительно ли вы имели это в виду.
Достаточно убрать из сборки -Werror
-Wall
-Wpedantic
, и вы сможете совершать преступления, связанные с многопоточностью, доступом только для чтения и аппаратными регистрами.
Это ведь справедливо, правда? Если кто-то убирает все эти флаги предупреждения/ошибки, то ему, очевидно, нет дела до того, какую бестактность или глупую оплошность вы совершили. Это означает, что в конечном итоге эти предупреждения не имеют никакого значения и безвредны с точки зрения соответствия стандартам ISO C. И все же...
Мы считаем изменения в предупреждениях нарушением совместимости
Да.
Это отдельный вид ада, с которым свыклись разработчики языка С, и в меньшей степени разработчики C++. Предупреждения воспринимаются как раздражители; и, как показывает любое включение -Weverything
или /W4
, таковыми они и являются. Предупреждения о затенении переменных в глобальном пространстве имен (спасибо, все заголовки и библиотеки С теперь являются проблемой), использование «зарезервированных» имен, и «для этой структуры включено выравнивание, потому что вы использовали оператор alignof
(... да, я знаю, что для неё включено выравнивание, я явно запросил его включить И ПОЭТОМУ Я ИСПОЛЬЗОВАЛ alignof
, мистер Компилятор) — все это невероятно затратно по времени.
Но это предупреждения.
Несмотря на то, что они раздражают, они помогают предотвратить проблемы. Тот факт, что я могу бездумно игнорировать все квалификаторы и ломать все виды безопасности, связанные с чтением, записью, потоками и неизменяемостью, становится серьезной проблемой, когда речь идет о коммуникации намерений и предотвращении багов. Даже старый синтаксис K&R приводил к ошибкам в промышленных и правительственных кодовых базах, потому что пользователи что-то делали неправильно. Это не потому, что они плохие программисты: это потому, что они работают с кодовыми базами, которые бывают старше их самих, и вынуждены иметь дело с техническим долгом на много миллионов строк кода. Не получится держать всю кодовую базу в голове: именно с этим должны бороться конвенции, статический анализ, высокие уровни предупреждений и все остальное. К сожалению,
всем нравится код без предупреждений.
Это означает, что в тот момент, когда разработчик GCC сделает предупреждение более чувствительным к потенциальным проблемным случаям, люди, поддерживающие кодовую базу (не изначальные разработчики) внезапно получат логи на несколько гигабайт, содержащие тонну предупреждений и прочих штук из их старого кода. «Это глупо», — скажут они, — «код работает уже ГОДАМИ, почему GCC начал жаловаться только сейчас?». Это означает, что избегаются даже такие действия, как добавление const
в сигнатуру функции, даже если это с моральной, духовной и фактической стороны правильно. Нарушение совместимости означает, что людям теперь придется смотреть в код, который имеет сомнительные намерения. Этот код из-за неопределенного поведения выведет из строя чип или испортит память, но это уже другая проблема разработки на C в современной экосистеме.
Возраст как мера качества
Сколько вообще людей догадались бы, что в sudo
есть уязвимость, столь же фантастически простая, как «-1 или целочисленное переполнение дает вам доступ ко всему»? Сколько людей думали о том, что Heartbleed станет реальной проблемой? Сколько разработчиков игр используют «крошечные» библиотеки stb, ни разу не запустив на них фаззинг и не поняв, что они содержат более значительные уязвимости ввода, чем они могли себе представить? Это не упрек в адрес ввышеперечисленных фрагментов кода или программистов, стоящих за ними: они предоставляют жизненно важную услугу, от которой мир зависел на протяжении десятилетий, часто практически без поддержки, пока все это не превратилось в большую проблему. Но люди, которые этому коду поклоняются и деплоят его, в конечном итоге поддерживают токсичную идею, появившуюся под влиянием ошибки выжившего:
«Этому коду так много лет, его использовали столько человек — как у него могут быть проблемы?».
Придерживаясь высших идеалов разработки кода на языке С — принципа обратной совместимости и стремления не становиться «источником неудобств, люди с приличным опытом в индустрии начинают приравнивать возраст к качеству, как если бы кодовые базы были бочками вина в погребе. Чем старше и дольше используется код, тем лучше и вкуснее вино.
Реальность, к сожалению, гораздо менее романтична: полный багов и уязвимостей, с каждым днем техдолг растет и становится все более опасным. Каждая система превращается в полуживую, непроработанную и частично необслуживаемую гниющую шелуху. Их приукрашивают с целью придать им благородный вид, но на самом деле это забальзамированный труп, который только и ждет, чтобы его ткнули пальцем не туда, и тогда его гнойные застарелые нарывы лопнут и зальют приложение своим годами выдержанным ботулизмом.
Окей... Мерзко, но как насчет стандарта C?
Проблема, которую я заметил за свое короткое пребывание в качестве члена комитета, заключается в том, что мы предпочитаем обратную совместимость всему остальному, делаем выбор в сторону старых приложений и их юзкейсов, а не какого-либо шанса улучшения надёжности, безопасности и читабельности кода для тех кто переходит на С, даже сегодня. Документ доктора Краузе настолько мал, что практически не вызывает споров: если кому-то не нравятся предупреждения, он может их отключить. Они являются предупреждениями, а не ошибками, не просто так: абстрактная машина языка С не требует диагностики, ISO C позволяет принимать этот код в самых строгих режимах сборки, а это предложение помогло бы остальному миру отучиться от API, в которых четко сказано: «Изменение содержимого того, что вам вернули, является неопределенным поведением».
И тем не менее, мы пересмотрели свое мнение о документе после того, как в качестве причины было приведено «мы не можем вводить новые предупреждения».
Аргументом против было, по сути, «если мы изменим эти сигнатуры, много кода сломается». Это, опять же, представляет изменение предупреждений как нарушение совместимости в поведении (помните, неявные преобразования, удаляющие квалификаторы — даже _Atomic
— вполне допустимы в ISO C, даже если это нарушение ограничений). Если бы это было так, каждый разработчик компилятора должен был бы ввести нечто подобное Rust epoch, но только для предупреждений, чтобы дать людям «стабильный» набор, с которым всегда можно проверять код. Это не совсем новое мнение — я читал статьи некоторых инженеров Coverity о том, как они работают над созданием новых предупреждений и о реакции клиентов на них. Управление «доверием разработчиков» к новым предупреждениям и другим вещам — сложная работа. Требуется много времени, чтобы убедить разработчиков в их полезности. Даже Джону Кармаку потребовалось время, чтобы получить от инструментов статического анализа надлежащий набор предупреждений и ошибок, подходящих для его разработки, прежде чем он пришел к выводу, что «не использовать статистический анализ — безответственно».
И все же, как комитет, мы боремся с добавлением const
к 4 возвращаемым значениям стандартных функций, потому что это добавит предупреждения к потенциально опасному коду. Мы возражали против признания устаревшим старого синтаксиса K&R, несмотря на видимые свидетельства как ошибок по незнанию, так и видимые уязвимости из-за того, что разработчики передают неправильные типы. Мы чуть не добавили неопределенное поведение в препроцессор, только для того, заставить одну особенную реализацию C «сделать все правильно». Мы всегда балансируем на грани того, чтобы сделать объективно неправильную вещь по причинам обратной совместимости. И это, дорогой читатель, в будущем С меня пугает больше всего.
Стандарт C вас не защищает
Не заблуждайтесь — что бы ни говорили вам программисты, поведение руководящего органа C совершенно ясно. Мы не будем вводить предупреждения в ваш старый код, даже если этот старый код может быть опасен. Мы не будем уводить вас от ошибок, потому что это может поколебать фасад, за которым станет видно, что то, что делает старый код, на самом деле неправильно. Мы не будем облегчать новым программистам написание лучшего кода на С. Мы не будем требовать, чтобы старый код соответствовал какому-либо стандарту. Каждую новую фичу мы сделаем необязательной, потому что мы не представляем себе, чтобы разработчики компиляторов придерживались более высоких стандартов, и не ожидаем большего от поставщиков стандартной библиотеки.
Мы позволим компилятору лгать вам. Мы будем лгать вашему коду. А когда что-то пойдет не так — ошибка, «опаньки», утечка данных — мы печально покачаем головой. Мы выразим свою поддержку и добавим: «Как жаль». Действительно, жаль...
Может быть, мы исправим это в другой раз, дорогой читатель.
А в заключение статьи приглашаем всех желающих разработчиков на C на открытый урок, посвященный стандарту С23. На нем рассмотрим:
устаревшие и удалённые возможности языка,
новые языковые конструкции,
изменения в стандартной библиотеке.
Записаться на открытый урок можно на странице курса «Программист С».
Комментарии (19)
GospodinKolhoznik
10.07.2023 13:09+7Слова составляются из букв, создают моё удобство чтения и процесс понимания содержания строчек. Перевод слов с языка истока в язык стока помогает прочитать ряды символов на привычном для каждого читателя алфавите. Скажем дружное спасибо небу над нами, за что оно балует нас радостными переводами публикаций.
marks
10.07.2023 13:09+6Эти ребята публикуют в день по 4-5 структурных наборов символов, которые им почему-то кажутся статьями.
GolovinDS
10.07.2023 13:09Также, как некоторые ребята, которые обиделись на то, что не смогли продать свои услуги, минусят теперь все статьи и оставляют комментарии под каждой ;)
marks
10.07.2023 13:09+2Глупости говорите. Я оставляю подобные комментарии только там, где явно нарушены правила Хабра. 5 статей в день — вам об этом говорил не только я, но и другие читатели. Но ту статью вы скрыли, а теперь пытаетесь прикрываться моей выдуманной "обидой".
Ну и если каждую вашу статью минусят — наверное, это повод задуматься над тем, что вы делаете и как делаете. Но, конечно, удобнее бросаться обвинениями, угу. Конструктивненько, учитывая, что у этой статьи уже 6 минусов. Конечно, контент качественный, это комментаторы плохие.
GolovinDS
10.07.2023 13:09Ну, если я скажу Вам, что мне известно о чатах, где сидят определенные "прокачанные авторы" и коллективно где нужно ставят плюсы, а где нужно - наоборот минусы, Вы наверняка скажете, что это тоже глупости. "Менеджеры" подобных чатов неоднократно пытались продать нам такого рода услуги, но спасибо, мы за честность.
Повторюсь, мы не ставим перед собой суперцель набрать максимальный рейтинг за счет количества статей, хотя не скрою, что это приятный бонус. Но, да, попадая в топ 3 количество как "предложений" так и негатива, заметно увеличивается, что тоже наталкивает на определенные мысли.
5 статей в день от разных авторов корпоративного блога не является нарушением правил.
marks
10.07.2023 13:09+3Да мне все равно, что вам там известно. Я о таких чатах впервые слышу и не понимаю, к чему вы это рассказываете. И уж совершенно точно я вам ничего никогда не "продавал". До того, как вы начали забивать Хабр низкопробным контентом в большом количестве, я о вас и не слышал ничего.
А то, что у вас увеличивается количество негатива, повторюсь, связано лишь с тоннами низкопробного контента. Топ-3 тут совершенно ни при чем, уверяю. К слову, похожую "стратегию" продвижения использовала пара компаний года три назад. Но там хоть контент был более-менее, у вас же вообще нет, посмотрите хотя бы на качество статей у корпоративных блогов из топ-3.
А то, что вы ставите перед собой суперцель набрать максимальный рейтинг за счет количества статей — это настолько очевидно, что я не знаю, зачем это отрицать.
Те пару компаний забанила администрация, несмотря на то, что "авторы разные". Что ж, подождем.
Hardcoin
10.07.2023 13:09+2Это неопределенное поведение и писать в них — неправильно, так что давайте перестанем издеваться друг над другом по этому поводу но
Но ведь это действительно плохой контент, вы разве не согласны? Это даже с точки зрения русского языка - издевательство. Нанятый вами человек умеет программировать на си?
GospodinKolhoznik
10.07.2023 13:09+4Ещё и карму слили только что. Видимо командой работают, и сливают тех, кто критикует качество перевода.
marks
10.07.2023 13:09Ну и потом рассказывают про то, что они за "честную игру". В итоге только что вышел их еще один пост с 16 просмотрами и сразу 2 плюсами :) А эти заминусованные посты за полчаса набрали по 5-7 плюсов. Конечно, их поставили независимые читатели, угу.
paluke
10.07.2023 13:09+2А зачем компилировать старый код новым компилятором?
0xd34df00d
10.07.2023 13:09+11Потому что старый код может быть частью живущей и развивающейся кодовой базы, в которой пишется и новый код, и при написании нового кода может хотеться использовать новые фичи языка/процессора, диагностики или оптимизации.
paluke
10.07.2023 13:09+1Ну если это часть живущей и развивающейся кодовой базы, может стоит и старый код поправить? В С++ приходилось править большую кодовую базу, когда захотели использовать новый стандарт. И вроде ничего сложного.
0xd34df00d
10.07.2023 13:09Если это предупреждение от компилятора — да, можно починить. Если новый компилятор использует больше UB для оптимизаций, что для программиста выглядит как внезапное «ломание кода», то такое починить сложно, потому что проблема вылезет только в рантайме в лучшем случае.
aegoroff
10.07.2023 13:09+1С отловом части UB могут помочь инструменты вроде PVS-Studio, правда они денег стоят
MinimumLaw
10.07.2023 13:09Я не знаю что думает комитет по стандартизации языка, но... Язык C - это довольно низкоуровневый язык. Никого не удивляет факт того, что на ассемблере можно отстрелить себе не только ногу, но и все остальное. Никого не удивляет что на VHDL можно плодить редкостную дичь (чем, собственно, он регулярно и занимается и почему важно смотреть на RTL). Любой низкоуровневый язык обязан позволять все, что только может сделать аппаратура. С точки зрения аппаратуры указатель - это просто адрес в памяти. Адресу этому можно присвоить абсолютно любой значение. А как трактовать данные - забота исключительно программиста.
Если вы хотите, чтоб язык вас защищал - Welcome to
HellRust! Но каким бы не был острым ножик, хлеб я все же предпочту резать им, а не безопасной хлеборезкой. И, пожалуй, картошку с морковкой чистить удобнее ножом, нежели картофелечисткой. Да и салатик покромсать проще и быстрее ножом, чем слайсером. И, безусловно, скальпель для хирурга пока незаменим. А то, что руки при этом ранятся... Ну что тут делать. Это проходит только с опытом. И то не до конца.
Gay_Lussak
10.07.2023 13:09+4Только при ембеддерах не говорите такие слова про volatile.
KarmaCraft
10.07.2023 13:09Потому что ембеддеры их не применяют, или потому что это в данной сфере это бесполезно? Спрашиваю потому, что сам задавался этим вопросом.
0xd34df00d
Потому что старый код может быть частью живущей и развивающейся кодовой базы, в которой пишется и новый код, и при написании нового кода может хотеться использовать новые фичи языка/процессора, диагностики или оптимизации.