Я перепроектировал какой-то код и наткнулся на это ...
/************************************************************************/
/* */
/* MACRO CHECK_FREAD */
/* */
/* CHECK_FREAD is used to check the status of a file read. It */
/* is passed the return code from read and a string to print out if */
/* an error is detected. If an error is found, an error message is */
/* printed out and the program terminates. This was made into a */
/* macro because it had to be done over and over and over . . . */
/* */
/************************************************************************/
#define CHECK_FREAD(X, msg) if (X==-1) \
{ \
return(DCD_BADREAD); \
}
По сути, конкретный набор функций в этом файле вызывает это всякий раз, когда они выполняют (c-чтение), чтобы проверить результат на наличие ошибки. У них также есть аналогичная проверка для EOF ... В основном, насколько я могу судить, они делают это таким образом, чтобы вставить возврат при ошибке в середине своей функции в кучу мест.
e.g.
/* Read in an 4 */
ret_val = read(fd, &input_integer, sizeof(int32));
CHECK_FREAD(ret_val, "reading an 4");
CHECK_FEOF(ret_val, "reading an 4");
if (input_integer != 4)
{
return(DCD_BADFORMAT);
}
/* Read in the number of atoms */
ret_val = read(fd, &input_integer, sizeof(int32));
*N = input_integer;
CHECK_FREAD(ret_val, "reading number of atoms");
CHECK_FEOF(ret_val, "reading number of atoms");
Сейчас я только возвращаюсь к программированию на c / c ++ и вообще никогда не использовал много макросов, но разве это злоупотребление макросами?
... и это не похоже ни на один из кошерных примеров, поэтому я предполагаю, что "ДА". Но я хотел получить более информированное мнение, а не просто делать обоснованные предположения ... :)
... эээ, а не лучше ли использовать какую-нибудь обертку?
Этот макрос не выводит сообщение об ошибке, как следует в соответствии с его описанием в комментариях. Это полный код? Kirill V. Lyadvinsky 02.09.2010
Да, это полный код. Что касается ChrisF, посмотрите пример, который я добавил. Jason R. Mick 02.09.2010
И почему аргумент msg не используется? Комментарии не соответствуют реализации, что означает, что по крайней мере один из них содержит ошибки, но, вероятно, оба. Было бы лучше написать size_t checked_fread(void *buffer, size_t itemsize, size_t numitems, FILE *fp, const char *msg) функцию и использовать ее последовательно. Также интересно порассуждать, почему он проверяет по -1; fread() возвращает 0 или короткий счетчик ошибок, а тип возврата - size_t, что исключает возвращаемое значение -1 почти для всех целей. Jonathan Leffler 02.09.2010
Но считается ли это злоупотреблением? Это мой вопрос. @Jonathan Я понятия не имею, почему они не используют msg ... забывчивость? лень? спутанность сознания? Вы выбираете! Но вы совершенно правы, это странно .... Jason R. Mick 02.09.2010
Вероятно, они использовали атрибут msg при тестировании кода и убрали код печати перед его выпуском. Сначала я подумал об этом как о способе вставки комментариев в код, но почему бы вместо этого не использовать честные / * комментарии * /? teukkam 02.09.2010
Злоупотребление против злоупотребления? Сложно сказать ... поскольку msg не используется, это несколько бессмысленно. Ясно, что комментарии по поводу fread() не имеют отношения к read(). Поскольку мы не видим CHECK_FEOF (), мы не можем сказать, что он делает. Однако мне кажется, что простая функция для обработки обоих условий была бы лучше: она могла бы обернуть функцию read() (системный вызов) и отделить статус ошибки от всего остального: if ((retval = wrapped_read(fd, &input_integer, sizeof(int32), "reading a 4")) != DCD_NOERROR) return retval;. И если тест на 4 будет повсеместным, я бы. Jonathan Leffler 02.09.2010
Потому что это не то же самое. Если вы поместите то же самое в функцию, у вас будет короткая функция, которая возвращает DCD_BADWRITE, если X равно -1. Однако рассматриваемый макрос возвращается из содержащей функции. Пример:
int foobar(int value) {
CHECK_FREAD(value, "Whatever");
return 0;
}
расширится до:
int foobar(int value) {
if (value == -1) {
return (DCD_BADWRITE);
};
return 0;
}
Однако если бы это была функция, внешняя функция (foobar) с радостью проигнорировала бы результат и вернула бы 0.
Макрос, который так похож на функцию, но ведет себя по-разному в решающей степени, является основным IMO. Я бы сказал, да, это злоупотребление макросами.
Ответ 2 · рейтинг 8 · Mark Ransom
Это невозможно сделать как функцию, потому что return возвращается из вызывающей функции, а не из блока внутри макроса.
Это из тех вещей, которые дадут макросам плохую репутацию, потому что они скрывают важную часть логики управления.
Ответ 3 · рейтинг 5 · bta
Я сомневаюсь, стоит ли называть такой макрос * злоупотреблением *, но скажу, что это плохая идея.
Я бы сказал, что в целом вам следует избегать использования return в макросе. Трудно создать макрос, который правильно обрабатывает возврат, не сделав поток управления вызывающей его функцией неудобным или трудным для понимания. Вы определенно не хотите "случайно" вернуться из функции, даже не осознавая этого. Отладка этого может раздражать.
Кроме того, этот макрос может вызвать проблемы с потоком кода в зависимости от того, как он используется. Например, код
if (some_condition)
CHECK_FREAD(val, "string")
else
something_else();
не будет делать то, что вы думаете (else становится ассоциированным с if внутри макроса). Макрос должен быть заключен в { }, чтобы убедиться, что он не изменяет окружающие условные выражения.
Также второй аргумент не используется. Очевидной проблемой здесь является реализация, не соответствующая документации. Скрытая проблема заключается в том, что макрос вызывается следующим образом:
Вы ожидаете, что указатель переместится к следующему элементу массива после вызова макроса. Однако второй параметр не используется, поэтому увеличение никогда не происходит. Это еще одна причина, по которой утверждения с побочными эффектами вызывают проблемы в макросах.
Вместо того, чтобы использовать макрос для всего этого, почему бы не написать функцию для инкапсуляции read и проверки ошибок? Он может вернуть ноль в случае успеха или код ошибки. Если код обработки ошибок является общим для всех блоков read, вы можете сделать что-то вроде этого:
int your_function(whatever) {
int ret;
...
if ((ret=read_helper(fd, &input_integer)) != 0)
goto error_case;
...
// Normal return from the function
return 0;
error_case:
print_error_message_for_code(ret);
return ret;
}
Пока все ваши вызовы read_helper присваивают свое возвращаемое значение ret, вы можете повторно использовать один и тот же блок кода обработки ошибок во всей функции. Ваша функция print_error_message_for_code просто примет код ошибки в качестве входных данных и использует switch или массив для печати соответствующего ему сообщения об ошибке.
Я допускаю, что многие люди боятся goto, но повторное использование общего кода обработки ошибок вместо ряда вложенных блоков и переменных состояния может быть подходящим вариантом использования для этого. Просто держите его в чистоте и читабельности (одна метка на функцию, а код обработки ошибок должен быть простым).
Ответ 4 · рейтинг 2 · Jens Gustedt
Является ли это злоупотреблением или нет - дело вкуса. Но я вижу в этом некоторые принципиальные проблемы.
документация полностью неверна
реализация очень опасна из-за if и возможной else проблемы с зависанием
название не предполагает, что это предварительный возврат окружающей функции
так что это определенно очень плохой стиль.
Ответ 5 · рейтинг 2 · Steve Jessop
Если макрос находится в исходном файле и используется только в этом файле, то я считаю его менее оскорбительным, чем если бы он был где-то в каком-то заголовке. Но мне не очень нравится макрос, который возвращает (особенно тот, который задокументирован для завершения приложения и фактически возвращает), и еще меньше тот, который возвращает условно, потому что он позволяет довольно легко создать следующая утечка памяти:
char *buf = malloc(1000);
if (buf == 0) return ENOMEM;
ret_val = read(fd, buf, 1000);
CHECK_READ(ret_val, "buffer read failed")
// do something with buf
free(buf);
Если я верю документации, у меня нет оснований подозревать, что этот код приводит к утечке памяти при сбое чтения. Даже если бы документация была правильной, я предпочитаю, чтобы поток управления на C был явно очевиден, что означает, что он не был скрыт макросами. Я бы также предпочел, чтобы мой код выглядел неопределенно согласованным между случаями, когда у меня есть ресурсы для очистки, и случаями, когда у меня их нет, вместо того, чтобы использовать макрос для одного, но не для другого. Так что макрос не в моем вкусе, даже если он не является однозначно оскорбительным.
Чтобы сделать то, что написано в документации, я бы предпочел написать что-то вроде:
check(retval != -1, "buffer read failed");
где check - функция. Или используйте assert. Конечно, assert делает что-либо, только если NDEBUG не установлен, поэтому он не годится для обработки ошибок, если вы планируете отдельные сборки отладки и выпуска.
Чтобы сделать то, что на самом деле делает макрос, я бы предпочел полностью пропустить макрос и написать:
if (retval == -1) return DCD_BADREAD;
Это вряд ли длинная строка кода, и нет никакой реальной пользы в объединении разных ее экземпляров. Также, вероятно, не стоит думать, что вы улучшите ситуацию, инкапсулируя / скрывая существующие, хорошо документированные средства, с помощью которых read указывает на ошибки, что широко понимается программистами на C. По крайней мере, эта проверка полностью игнорирует тот факт, что read может производить меньше данных, чем вы запрашивали, без видимых причин. Это не ошибка макроса напрямую, но вся идея, лежащая в основе макроса, похоже, исходит из плохой проверки ошибок.
Вероятно, здесь произошло то, что раньше у них был макрос, который завершался, а затем они решили, что не хотят немедленно прерывать программу при возникновении сбоев, вместо этого они хотели вернуть этот код ошибки DCD_BADREAD. Что они должны были сделать, так это полностью удалить макрос и изменить все вызывающие сайты - если вы измените логику управления функцией и измените то, что она возвращает, лучше всего заметно изменить источник этой функции. Вместо этого они сделали минимально возможное изменение, которое дало желаемый результат. В то время это, вероятно, казалось хорошей идеей (и, к сожалению, забвение обновлять комментарии - распространенная ошибка). Но, как кажется, все согласны, в результате получился очень хитрый код.
Макросы, которые выполняют поток управления, могут быть неприемлемыми, но я думаю, только в тех случаях, когда они выглядят как поток управления и точно задокументированы. На ум приходят макросы для совместной подпрограммы Саймона Тэтхема - некоторым людям в первую очередь не нравятся совместные подпрограммы, но, если вы принимаете эту предпосылку, макрос с именем crReturn по крайней мере выглядит так, как будто он вернется. .
Ответ 6 · рейтинг 1 · supercat
Я бы рассмотрел пример злоупотребления макросом по двум причинам: (1) имя макроса не проясняет, что он делает, в первую очередь то, что он может возвращать; (2) Макрос синтаксически не эквивалентен оператору. Код вроде
if (someCondition)
CHECK_FREAD(whatever);
else
do_something_else();
не удастся. Мое предпочтительное изменение:
#define LOG_AND_RET_ERROR(msg) do {LOG_ERROR(msg); return DCD_BADREAD;} while(0)
if (x==-1) LOG_AND_RET_ERROR(msg);
Ответ 7 · maxschlepzig
Что ж, если вы определяете короткую функцию, вам нужно вводить больше символов на каждом вызывающем сайте. Т.е.
CHECK_FREAD(X, msg)
vs.
if (check_fread(X, msg)) return DCD_BADREAD;
Ответ 8 · ezpz
В ограниченном пространстве это было реализовано, возможно, все было хорошо (хотя в C ++ это обычно не одобряется).
Больше всего меня беспокоит то, что какой-нибудь недавно представленный разработчик может это увидеть и подумать о том, что делать.
if (CHECK_FREAD(...)) {
/* do stuff here */
} else {
/* error handle */
}
Что явно неверно.
Кроме того, кажется, что msg не используется, если не используется / не ожидается в DCD_BADREAD, что ухудшает ситуацию ...