Я 15 лет пишу на C++, немного знаю C#, Python и пр. Но совершенно не понимаю, как писать на C. Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp). RAII нет, значит надо в конце функции все чистить самому. Знаю, что у MS в драйверах используется goto cleanup для этих целей. Что-то типа:
Понятно, что можно использовать alloca для автоосвобождения памяти. Но не везде это подходит (стек все-таки ограничен). Далее, в C++ есть STL с кучей полезных структур данных (а есть ведь еще Boost/Poco). В чистом C ничего такого нет, т.е. надо использовать какие-то сторонние библиотеки. Какие?
Может есть какие-нибудь книги? Думаю, посмотреть что-то типа Writing device drivers for Linux.
Здравствуйте, Lonely Dog, Вы писали:
LD>Я 15 лет пишу на C++, немного знаю C#, Python и пр. Но совершенно не понимаю, как писать на C. Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp). RAII нет, значит надо в конце функции все чистить самому.
Ну да, так и пишем, все руками. Полезность C++'ных автоматизмов сильно переоценена. В том смысле, что необходимость делать все явно добавляет, конечно, работы, но не так, чтобы очень много. С другой стороны, Си не подбросит сюрприза от того, что что-то автоматически случилось, с неочевидными побочными эффектами, а где и что, поди пойми.
Обработка ошибок в виде явно возвращаемых кодов возврата — это не такой уж и плохой вариант. В отличии от исключений, все перед глазами и все легко читается, ошибка не прилетит неизвестно откуда и не улетит неизвестно куда. setjmp/longjmp для обработки ошибок очень мало подходят, это экзотические функции, которые бывают полезны в редких случаях.
LD>Знаю, что у MS в драйверах используется goto cleanup для этих целей. Что-то типа:
Да, это стандартный паттерн, когда надо сделать длинную последовательность действий (сложная инициализация, конструирование сложного объекта и т.п.) и хочется иметь централизованную обработку ошибок на случай, если что-то пошло не так. Достоинством такого подхода является то, что саму сложную последовательность можно редактировать, не боясь сломать обработку ошибок.
LD>Понятно, что можно использовать alloca для автоосвобождения памяти. Но не везде это подходит (стек все-таки ограничен).
alloca освобождает память, но не закрывает файлы и т.п. Кроме того, с указателями, полученными от alloca, надо быть осторожным. Они становятся невалидными при выходе из функции, и могут стать невалидными при выходе из блока (я на такое нарывался, наверное, это был глюк компилятора, но кому от этого легче?).
LD> Далее, в C++ есть STL с кучей полезных структур данных (а есть ведь еще Boost/Poco). В чистом C ничего такого нет, т.е. надо использовать какие-то сторонние библиотеки. Какие?
В каждом крупном проекте, типа linux kernel, solaris kernel, postfix, nginx etc, есть свой аналог STL. Свои map-ы, list-ы, unordered_map-ы, свой джентельменский набор содержимого STL-вского <algorithm>, и ещё куча всего, чего в STL- нет и не будет, просмотрите содержимое этой папки.
Здравствуйте, Lonely Dog, Вы писали:
LD>Я 15 лет пишу на C++, немного знаю C#, Python и пр. Но совершенно не понимаю, как писать на C. LD>Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp). RAII нет, значит надо в конце функции все чистить самому.
Ручками, ручками... Указатель на экземпляр класса передавать — сам, таблицу виртуальных методов сделать — сам, ресурсы подчистить — сам, проверить, что коды ошибок не игнорируются и результаты несработавших методов не используются — сам.
Мне представляется, что С++ первоначально набирал популярность как раз за счет перекладывания на компилятор многих существовавших рутинных аспектов программировании на С.
LD>Может есть какие-нибудь книги? Думаю, посмотреть что-то типа Writing device drivers for Linux.
Надо полагать, что в драйверах (и вообще в коде ОС) есть своя специфика.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Здравствуйте, Lonely Dog, Вы писали:
> Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp).
Обработка исключений в С++ слишком дорогая операция, и для проверки результатов работы каждой функции это явно не годится.
> RAII нет, значит надо в конце функции все чистить самому. Знаю, что у MS в драйверах используется goto cleanup для этих целей. Что-то типа:
RAII тоже сомнительная фича. Сейчас всякая школота начинает пихать в конструкторы открытие дескрипторов, операции ввода/вывода, операции, требующие доступ к разделяемому ресурсу и пр. "тяжёлые" операции которые могут закончиться неудачно и, тем самым, экземпляр объекта будет непригоден для использования. Это глуповатый подход. Правильней создавать специальные фукции в духе init/create, вызывать их перед использоваием экземпляра, с проверкой результата инициализации. Корифеи индустрии так и поступают
S>RAII тоже сомнительная фича. Сейчас всякая школота начинает пихать в конструкторы открытие дескрипторов, операции ввода/вывода, операции, требующие доступ к разделяемому ресурсу и пр. "тяжёлые" операции которые могут закончиться неудачно и, тем самым, экземпляр объекта будет непригоден для использования. Это глуповатый подход. Правильней создавать специальные фукции в духе init/create, вызывать их перед использоваием экземпляра, с проверкой результата инициализации. Корифеи индустрии так и поступают
Ваши корифеи и unininit поди перед каждым return'ом ставить не забывают никогда?
А вот с мнением о помещении "сложного" кода в конструкторы я согласен.
Здравствуйте, Pzz, Вы писали:
Pzz>Ну да, так и пишем, все руками. Полезность C++'ных автоматизмов сильно переоценена. В том смысле, что необходимость делать все явно добавляет, конечно, работы, но не так, чтобы очень много.
Error-prone, легко забыть и трудно поддерживать.
Pzz>Обработка ошибок в виде явно возвращаемых кодов возврата — это не такой уж и плохой вариант. В отличии от исключений, все перед глазами и все легко читается, ошибка не прилетит неизвестно откуда и не улетит неизвестно куда.
Error codes точно также летят наверх неизвестно куда.
Хотя часто вообще не летят, ибо на них забивают — пример
Здравствуйте, Lonely Dog, Вы писали:
LD>RAII нет, значит надо в конце функции все чистить самому.
RAII можно частично сэмулировать через for + макросы, будет эдакий аналог using из C# / with из Python / try-with-resources из Java.
(for позволяет выполнить заданный код (cleanup) в конце блока for(...){ ... } /*auto_cleanup*/ )
B>>Ваши корифеи и unininit поди перед каждым return'ом ставить не забывают никогда?
S>Забывают? Тут про разработчиков ПО говорим, или про пациентов клиники для склеротиков?
Здравствуйте, Lonely Dog, Вы писали:
LD>Привет!
LD>Я 15 лет пишу на C++, немного знаю C#, Python и пр. Но совершенно не понимаю, как писать на C. Как организовывать обработку ошибок (исключений нет, значит остаются коды возврата или setjmp/longjmp). RAII нет, значит надо в конце функции все чистить самому. Знаю, что у MS в драйверах используется goto cleanup для этих целей. Что-то типа: LD>Понятно, что можно использовать alloca для автоосвобождения памяти. Но не везде это подходит (стек все-таки ограничен). Далее, в C++ есть STL с кучей полезных структур данных (а есть ведь еще Boost/Poco). В чистом C ничего такого нет, т.е. надо использовать какие-то сторонние библиотеки. Какие?
Сторонних библиотек предостаточно: http://sglib.sourceforge.net/ https://sourceforge.net/projects/ctl/ https://developer.gnome.org/glib/2.50/ http://docs.libuv.org/en/v1.x/
...
На чистом С несколько иной подход. Тут более важна организация кода, а не конкретные локальные приёмы. Совершенно не обязательно тащить универсальность из C++ иногда есть требования запрещающие использование динамической памяти и вполне себе компилируется и работает на ограниченных ресурсах с заранее выделенными массивами для обработки.
LD>Может есть какие-нибудь книги? Думаю, посмотреть что-то типа Writing device drivers for Linux.
Книги тут можешь глянуть http://rsdn.org/forum/cpp/6568718.1
B>>Как там в стране единорогов?
S>Значит, кто-то всё таки пишет код вместе с пациентами клиники для склеротиков. Но не все разрабы таковыми являются, это нужно понимать.
Я пишу код вместе с людьми, а людям, как известно, свойственно ошибаться. Вы хотите сказать, что ни разу не встречали ни одной ошибки, связанной с пропущенной деинициализацией?
Здравствуйте, kov_serg, Вы писали:
_>На чистом С несколько иной подход. Тут более важна организация кода, а не конкретные локальные приёмы.
RAII и исключения это глобальные приёмы. Про "иной подход" на C можно вот тут почитать.
_>Совершенно не обязательно тащить универсальность из C++ иногда есть требования запрещающие использование динамической памяти и вполне себе компилируется и работает на ограниченных ресурсах с заранее выделенными массивами для обработки.
Здравствуйте, smeeld, Вы писали:
S>Обработка исключений в С++ слишком дорогая операция,
По сравнению с чем? По сравнению с полным отказом от обработки ошибок — то да, дорогая. А если сравнивать с ручной обработкой ошибок — так можно сказать ничего не стоит.
S>и для проверки результатов работы каждой функции это явно не годится.
Аааа, вот оно что, вы неправильно пользуетесь исключениями.
S>RAII тоже сомнительная фича.
Это единственно адекватный способ управления ресурсами. Другой вариант — это использование сборщиков мусора, но для задач, решаемых с помощью С++ это не вариант. Да и RAII удобней в силу своей предсказуемости.
S>Сейчас всякая школота начинает пихать в конструкторы открытие дескрипторов, операции ввода/вывода, операции, требующие доступ к разделяемому ресурсу и пр.
А не школота, как я понимаю, для управления ресурсами не будет использовать RAII и при исключениях (и не только) страдать от утечек памяти, ресурсов, и недосозданных/битых объектов? А потом героических их фиксить костылями и считать, что С++ "плохой язык, потому что в нём течёт память"?
S>"тяжёлые" операции которые могут закончиться неудачно и, тем самым, экземпляр объекта будет непригоден для использования. Это глуповатый подход. эта проблема возникает из-за отказа от RAII, как я и написал выше
S>Правильней создавать специальные фукции в духе init/create, вызывать их перед использоваием экземпляра, с проверкой результата инициализации.
Чем правильней, почему правильней?
S>Корифеи индустрии так и поступают Ну это же смешной аргумент, не? Потому, что те же корифеи и лажу гонят тоже. Вообще это глупо создавать себе кумиров — всё надо пропускать через свой мозг.
Подход init/create используется чтобы подвязать С-код к плюсовому объекту, либо при использовании паттернов "шаблонный метод"/"строитель". А в целом для С++ оно ненужно.
Здравствуйте, b0r3d0m, Вы писали:
B>Как там в стране единорогов?
В нашей версии страны унихренов, они же единороги, давно изобрели статический анализ кода и такую организацию владения ресурсами, что бы не быть критически зависимым от RAII, а что в вашей не в курсе...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Lonely Dog, Вы писали:
LD>Понятно, что можно использовать alloca для автоосвобождения памяти. Но не везде это подходит (стек все-таки ограничен). Далее, в C++ есть STL с кучей полезных структур данных (а есть ведь еще Boost/Poco). В чистом C ничего такого нет, т.е. надо использовать какие-то сторонние библиотеки. Какие?
А что за С? Если 99, то там есть массивы с динамическим размером, так что alloca не особо понятно зачем нужна...
Если на С# писал, что тебе будет наверное удобно так организовывать код, что бы ресурсы регились в глобальном менеджере, и через него же и освобождались. Можно сделать такой менеджер, что бы у него можно было брать состояния, и потом к ним откатывать
Это будет работать похоже на GC в шарпе...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
E>В нашей версии страны унихренов, они же единороги, давно изобрели статический анализ кода и такую организацию владения ресурсами, что бы не быть критически зависимым от RAII, а что в вашей не в курсе...
Статический анализ, который выявляет невызванные в определённых местах функции наподобие cleanup?
Здравствуйте, push, Вы писали:
S>>и для проверки результатов работы каждой функции это явно не годится. P>Аааа, вот оно что, вы неправильно пользуетесь исключениями.
Про откат на самые начала, при возникновении ошибки, с помощью исключений всем известно, выше говорилось про контроль результатов вызова функций, ошибки в которых не должны приводить к откату на самые начала. У Вас только два варианта, смотреть возврат из функции, или бросать в ней исключение и ловить их сразу за вызовом этой функции. Второй вариант на порядок более затратный по количеству выполняемых процессором операций для обработки ошибки исполнения функции, чем первый.
P>Это единственно адекватный способ управления ресурсами. Другой вариант — это использование сборщиков мусора, но для задач, решаемых с помощью С++ это не вариант. Да и RAII удобней в силу своей предсказуемости.
Вот у Вас объект, он должен содержать установленное соединение с удалённым хостом, у нас RAII, поэтому соединение должно устанавливаться в конструкторе (типа никаких init/create). Более того, мы, как крутые C++-ники, обязательно сделаем emplace этого объекта в какой-нибудь контейнер, так как экземпляров этих объектов в программе создаётся много и чтоб руками их не уничтожать, нужно чтоб они уничтожались автоматически, которые не были унитожены в процессе исполнения. Так же мы не должны откатываться куда-то далеко, так как ошибка в установлении одного соединения, не значит, что будет ошибка в установлении другого соединения, при создании другого экземпляра. Что это значит? А то, что при использовании каждого экземпляра, нам нужно будет постоянно проверять, установилось ли там соединение в конструкторе при создании экземпляра, или нет. То есть, код везде будет содержать пресловутое if(is_good()) then_cool(), на что будут затрачиваться процессорные такты. На фоне этого init/create, вызываемое единожды при создании экземпляра такого объекта перед помещением его в контейнер, будет более эффективным решением. Там что-то говорили про сборщик мусора, память далеко не единственный ресурс, который может выделяться для объекта, и он самый безпроблемный по части выделения ресурс.
P>А не школота, как я понимаю, для управления ресурсами не будет использовать RAII и при исключениях (и не только) страдать от утечек памяти, ресурсов, и недосозданных/битых объектов?