В исходном коде было бы полезно использовать несколько операторов возврата - нет необходимости переключаться между кодом очистки возврата ошибки. Кроме того, обычно вам нужно освободить выделенное пространство при обычном возврате - иначе вы теряете память. И вы можете переписать пример без goto
, если вы будете осторожны. Это тот случай, когда вы можете с пользой объявлять переменные до того, как это необходимо:
void func()
{
char *p1 = 0;
char *p2 = 0;
char *p3 = 0;
if ((p1 = malloc(16)) != 0 &&
(p2 = malloc(16)) != 0 &&
(p3 = malloc(16)) != 0)
{
// Use p1, p2, p3 ...
}
free(p1);
free(p2);
free(p3);
}
Когда после каждой операции выделения есть нетривиальные объемы работы, вы можете использовать метку перед первой из операций free()
, и goto
в порядке - обработка ошибок является основной причиной использования goto
в наши дни, и что-то еще несколько сомнительно.
Я смотрю за кодом, в котором есть макросы со встроенными операторами goto. При первом знакомстве сбивает с толку возможность увидеть метку, на которую «не ссылается» видимый код, но которую нельзя удалить. Я предпочитаю избегать таких практик. Макросы в порядке, когда мне не нужно знать, что они делают - они просто делают это. Макросы не так хороши, когда вы должны знать, что они расширяют, чтобы использовать их точно. Если они не скрывают информацию от меня, это скорее неприятность, чем помощь.
Иллюстрация - имена, замаскированные для защиты виновных:
#define rerrcheck if (currval != &localval && globvar->currtub && \
globvar->currtub->te_flags & TE_ABORT) \
{ if (globvar->currtub->te_state) \
globvar->currtub->te_state->ts_flags |= TS_FAILED;\
else \
delete_tub_name(globvar->currtub->te_name); \
goto failure; \
}
#define rgetunsigned(b) {if (_iincnt>=2) \
{_iinptr+=2;_iincnt-=2;b = ldunsigned(_iinptr-2);} \
else {b = _igetunsigned(); rerrcheck}}
На rgetunsigned()
есть несколько десятков вариантов, которые несколько похожи - разные размеры и разные функции загрузчика.
Одно место, где они используются, содержит этот цикл - в большем блоке кода в одном случае большого коммутатора с некоторыми маленькими и некоторыми большими блоками кода (не особенно хорошо структурированные):
for (i = 0 ; i < no_of_rows; i++)
{
row_t *tmprow = &val->v_coll.cl_typeinfo->clt_rows[i];
rgetint(tmprow->seqno);
rgetint(tmprow->level_no);
rgetint(tmprow->parent_no);
rgetint(tmprow->fieldnmlen);
rgetpbuf(tmprow->fieldname, IDENTSIZE);
rgetint(tmprow->field_no);
rgetint(tmprow->type);
rgetint(tmprow->length);
rgetlong(tmprow->xid);
rgetint(tmprow->flags);
rgetint(tmprow->xtype_nm_len);
rgetpbuf(tmprow->xtype_name, IDENTSIZE);
rgetint(tmprow->xtype_owner_len);
rgetpbuf(tmprow->xtype_owner_name, IDENTSIZE);
rgetpbuf(tmprow->xtype_owner_name,
tmprow->xtype_owner_len);
rgetint(tmprow->alignment);
rgetlong(tmprow->sourcetype);
}
Не очевидно, что код там пронизан операторами goto! И ясно, что полное изложение грехов кода, из которого он исходит, займет весь день - их много и они разнообразны.