Довольно часто приходится захватывать некоторые ресурсы по принципу "все, либо ничего". Например, необходимо выделить память под структурку, а потом еще и для ее членов, далее открыть файл и записать его хэндл в эту структуру и в конце концов вернуть указатель на эту самую структуру. Но если что-то упало, то необходимо освободить все ранее захваченное и вернуть 0.
На мой взгляд, вариант с goto более естественен и с точки зрения логики, и с точки зрения производительности. Это касается языка C. В C++ ситуация получше: статусная переменная, хелпер классы (конструктор — захват ресурса, деструктор — освобождение в случае плохого значения статусной переменной) и исключения.
Но все дело в том, что лично мне писать приходится на C. Какие еще могут быть варианты проведения отката?
25.01.06 07:41: Перенесено модератором из 'Философия программирования' — VladD2
Здравствуйте, g_i, Вы писали:
g_i>Здравствуйте, ansi, Вы писали:
g_i>А как-нить так не проще?
Нет, потому что у тебя здесь сразу два допущения.
1) То, что ресурсы можно безопасно освободить, не зная были ли они получены
2) Что выделения ресурсов идут подряд и их можно впехнуть в if
Так не бывает (с)
В С goto для таких случаев рулит, с этим ничего не поделаешь.
Q>Нет, потому что у тебя здесь сразу два допущения.
Единственное допущение это то, что NULL представлятся в виде (void*)0
Во всем остальном функции логически идентичены.
Q>В С goto для таких случаев рулит, с этим ничего не поделаешь.
Он рулит, но не для таких случаев.
Q>Нет, потому что у тебя здесь сразу два допущения. Q>1) То, что ресурсы можно безопасно освободить, не зная были ли они получены Q>2) Что выделения ресурсов идут подряд и их можно впехнуть в if
Q>Так не бывает (с)
Q>В С goto для таких случаев рулит, с этим ничего не поделаешь.
Из кода, приведенного в примере следуют эти самые допущения.
Согласен, что в общем случае все не так просто. А частный, наверное, не интересен.
Здравствуйте, ansi, Вы писали:
A>Довольно часто приходится захватывать некоторые ресурсы по принципу "все, либо ничего". Например, необходимо выделить память под структурку, а потом еще и для ее членов, далее открыть файл и записать его хэндл в эту структуру и в конце концов вернуть указатель на эту самую структуру. Но если что-то упало, то необходимо освободить все ранее захваченное и вернуть 0.
A>Пример с выделением памяти: A>
A>typedef struct
A>{
A> char *url;
A> char *tag;
A> char *last_modified;
A> wchar_t *tmp_name;
A>} dl_record_t;
A>
A>Но все дело в том, что лично мне писать приходится на C. Какие еще могут быть варианты проведения отката?
Исправил ваш первый вариант (каждый этап отката не дублируется и никаких goto)
Владек wrote: > Исправил ваш первый вариант (каждый этап отката не дублируется и никаких > goto)
Ууууххх. Такое же читать невозможно. Особенно если еще пара уровней
вложенности будет.
Здравствуйте, Cyberax, Вы писали:
C>Владек wrote: >> Исправил ваш первый вариант (каждый этап отката не дублируется и никаких >> goto) C>Ууууххх. Такое же читать невозможно. Особенно если еще пара уровней C>вложенности будет.
Ну тогда обнулять структуру перед аллокацией полей и чуть-что сразу вызывать dealloc_всех_полей_и_структуры() и return NULL. Не нравится мне это дублирование вызовов просто.
Игра в городки развивает силу рук и меткость глаз. (С) Годзилла
Здравствуйте, ansi, Вы писали:
A>На мой взгляд, вариант с goto более естественен и с точки зрения логики, и с точки зрения производительности. Это касается языка C. В C++ ситуация получше: статусная переменная, хелпер классы (конструктор — захват ресурса, деструктор — освобождение в случае плохого значения статусной переменной) и исключения.
А на мой взгляд этот код весь чистейший бред. Вот как бы это написал бы я:
static dl_record_t * alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
{
dl_record_t *record = (dl_record_t*)calloc(sizeof(dl_record_t));
if (!record)
return NULL;
record->url = (char*)malloc(url_len + 1);
record->tag = (char*)malloc(tag_len + 1);
record->last_modified = (char*)malloc(last_modified_len + 1);
record->tmp_name = (wchar_t*)malloc((tmp_name_len + 1) * sizeof(wchar_t));
// Если не удалось занять память хотья бы под одну структуру данных...if (!(record->url && record->tag && record->last_modified && record->tmp_name))
{
// Освобождаем все к чертям
free(record->url); // если free() передать NULL, то она просто ничего не сделает.
free(record->tag);
free(record->last_modified);
free(record);
return NULL;
}
return record;
}
Теперь поясню почему все так, а не иначе.
Дело в том, что ситуация в которой память не выделится крайне редка. Это уже исключительная ситуация. В 99% случаев память будет выделна и проблем не будт.
Так что заниматься такой мудреной обработкой ошибок, каждый раз тратя время на тучу ненужных проверок просто глупо.
Важно, чтобы код работал быстро и эффективно в тех самых 99% случаев. А вот в нешатной ситуации можно и произвести несколько лишних вызовов.
Так же важно, чтобы код связанный с основной логикой работы приложения был компактным и понятным. Вынесение проверок успешности в концец, а то и в отдельную функцию очень способствует этому.
Ну, и главное! Казалось бы... причем тут goto?
... << RSDN@Home 1.2.0 alpha rev. 628>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, ansi, Вы писали:
A>Довольно часто приходится захватывать некоторые ресурсы по принципу "все, либо ничего". Например, необходимо выделить память под структурку, а потом еще и для ее членов, далее открыть файл и записать его хэндл в эту структуру и в конце концов вернуть указатель на эту самую структуру. Но если что-то упало, то необходимо освободить все ранее захваченное и вернуть 0.
A>Пример с выделением памяти:
Ты знаешь, именно так я и написал сначала Но! Проц не интеловский (арм, по-моему). Чтение 16 бит по нечетному адресу...и падает весь телефон Возиться с выравниванием не хотелось. Да и вообще, память там разбита на блоки фиксированного размера (начиная с кучи блоков по 4байт, ..., и заканчивая парой блоков по 64Кб), причем блоки не объединяются. Так что вероятность не получить один большой блок больше, чем вероятность не получить несколько блоков размером поменьше.
CS>А еще есть такие штуки как memory pool из APR.
Здравствуйте, VladD2, Вы писали:
A>>На мой взгляд, вариант с goto более естественен и с точки зрения логики, и с точки зрения производительности. Это касается языка C. В C++ ситуация получше: статусная переменная, хелпер классы (конструктор — захват ресурса, деструктор — освобождение в случае плохого значения статусной переменной) и исключения.
VD>А на мой взгляд этот код весь чистейший бред. Вот как бы это написал бы я: VD>
VD>static dl_record_t * alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
VD>{
VD> dl_record_t *record = (dl_record_t*)calloc(sizeof(dl_record_t));
VD> if (!record)
VD> return NULL;
VD> record->url = (char*)malloc(url_len + 1);
VD> record->tag = (char*)malloc(tag_len + 1);
VD> record->last_modified = (char*)malloc(last_modified_len + 1);
VD> record->tmp_name = (wchar_t*)malloc((tmp_name_len + 1) * sizeof(wchar_t));
VD> // Если не удалось занять память хотья бы под одну структуру данных...
VD> if (!(record->url && record->tag && record->last_modified && record->tmp_name))
VD> {
VD> // Освобождаем все к чертям
VD> free(record->url); // если free() передать NULL, то она просто ничего не сделает.
VD> free(record->tag);
VD> free(record->last_modified);
VD> free(record);
VD> return NULL;
VD> }
VD> return record;
VD>}
VD>
А на мой взгляд, код q_i все-же лучше. Идея та же, но при падении одного вызова, все остальные просто не выполняются. Ты же идешь до последнего
VD>Теперь поясню почему все так, а не иначе.
VD>Дело в том, что ситуация в которой память не выделится крайне редка. Это уже исключительная ситуация. В 99% случаев память будет выделна и проблем не будт.
VD>Так что заниматься такой мудреной обработкой ошибок, каждый раз тратя время на тучу ненужных проверок просто глупо.
Согласен, но проверки в любом случае нужны. Надежность — основное требование. Потому как если что-то где-то падает, то оно тянет за собой абсолютно все.
VD>Важно, чтобы код работал быстро и эффективно в тех самых 99% случаев. А вот в нешатной ситуации можно и произвести несколько лишних вызовов. VD>Так же важно, чтобы код связанный с основной логикой работы приложения был компактным и понятным. Вынесение проверок успешности в концец, а то и в отдельную функцию очень способствует этому.
Вот серьезно, есть ли проблемы с пониманием варианта с goto?
Да, в принципе согласен и склоняюсь к этому варианту, потому как других более разумных просто нет — goto находится под строжайшим запретом! Не дай Бог выставить код с goto на инспекцию — инфаркт инспекторам обеспечен
VD>Ну, и главное! Казалось бы... причем тут goto?
Здравствуйте, g_i, Вы писали:
g_i>А как-нить так не проще?
Да, вариант неплохой! Но, у меня calloc в распоряжении нет, да и malloc'а тоже. Менеджер свой. Можно конечно и написать нечто вроде calloc, но уже не то (с). Поэтому аллоцирование структуры придется вынести из if'а и добавить memset. К счастью, аналог функции free освобождает нулевой указатель без проблем. Но это не со всеми ресурсами.
Замечу: вариант с goto не требует зануления структуры и проверок на выделенность ресурсов.
Здравствуйте, ansi, Вы писали:
A>Согласен, но проверки в любом случае нужны. Надежность — основное требование. Потому как если что-то где-то падает, то оно тянет за собой абсолютно все.
А вот меня всегда интересовал вопрос, что делать, если вызов malloc вернул NULL для объекта, который не создать нельзя? Т.е., предположим, что у нас есть два конкурирующих за память приложения, которые в процессе своей работы исчерпали всю доступную память и хотят еще ее откушать. В один из моментов, в одном из приложений, на критичном участке кода, который нельзя не выполнить, malloc вернул NULL. Что делать?
Здравствуйте, Anton Batenev, Вы писали:
AB>А вот меня всегда интересовал вопрос, что делать, если вызов malloc вернул NULL для объекта, который не создать нельзя? Т.е., предположим, что у нас есть два конкурирующих за память приложения, которые в процессе своей работы исчерпали всю доступную память и хотят еще ее откушать. В один из моментов, в одном из приложений, на критичном участке кода, который нельзя не выполнить, malloc вернул NULL. Что делать?
Очевидно, что откат, но только чтоб никто не заметил Такие ситуации довольно редки, на мой взгляд. Если отказ и будет получен, то скорее всего работоспособность можно сохранить, потому как нехватает ресурсов только для какой-то части функционала, а ронять все не надо. Но если работоспособность сохранить невозможно, то придется аккуратно (или с треском — кому как нравится) вывалиться — другого выхода просто нет...
Anton Batenev wrote: > А вот меня всегда интересовал вопрос, что делать, если вызов malloc > вернул NULL для объекта, который *не* создать нельзя? Т.е., предположим, > что у нас есть два конкурирующих за память приложения, которые в > процессе своей работы исчерпали всю доступную память и хотят еще ее > откушать. В один из моментов, в одном из приложений, на критичном > участке кода, который нельзя не выполнить, malloc вернул NULL. Что делать?
Падать.
Как вариант — пытаться освободить кэши/временные данные и снова
повторить вызов.
A>static dl_record_t *
A>alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
A>{
A> dl_record_t *record = (dl_record_t *)malloc(sizeof(*record));
...
A> fail_tmp_name: free(record->last_modified);
A> fail_last_modified: free(record->tag);
A> fail_tag: free(record->url);
A> fail_record_url: free(record);
A> return 0;
A>}
A>
Этот вариант мне не нравится тем, что две не сильно связанные по логике части alloc_record (аллокация и деаллокация) оказываются сильно связанны порядком действий. Стоит только кому-то переместить выделение памяти для tmp_name в начало секции аллокации и оставить неизменной секцию деаллокации, как весь механизм рушится. А такое перемещение вполне возможно. Например, если мы знаем, что больше всего памяти требуется именно для tmp_name, то лучше попробовать выделить эту память вначале и, только если это удалось, выделять память под остальные поля.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.