1. Можно поизвращаться с setjmp/longjmp, эмулировать на них исключения
2. RAII, http://en.wikipedia.org/wiki/RAII
В Си это будет автоматические переменные, на стеке, блок { auto int x = ...} по которому автоматически автоматические переменные самоуничтожатся
3. Может, что-то можно придумать с GCC расширением про адресуемые метки, "labels as values":
---------------8<---------------------
typedef void *Inst;
void engine()
{
static Inst program[] = { &&add /* ... */ };
Inst *ip = program;
int *sp;
goto *ip++; /* dispatch first VM inst. */
add:
sp[1]=sp[0]+sp[1];
sp++;
goto *ip++; /* dispatch next VM inst. */
}
====================8<===============
Figure 1: Direct threaded code using GNU C’s “labels as values”
то есть, у тебя будет код вроде:
(подставляется в что-то вроде:)
CLEANUP(LB1,1)
{ auto STATUS result1=init1( ... );
if (!result1) {goto LB1}
...
LB1:
clean1();
}
CLEANUP(LB1,1)
{ auto STATUS result1=init1(..);
if (!result1) {goto labels[1]};
...
LB1:
clean1();
}
resultN, labels[N] (а не *curlabel++) -- чтобы сохранялась прозрачность абстракции макры, чтобы можно было вкладывать их друг в друга (CLEANUP в CLEANUP).
В принципе, CLEANUP можно обернуть другой макрой, которая будет это N корректно вести по вложенности
У меня их тоже можно вкладывать, но прийдется вызывать все макры с именем блока.
CLEANUPBLK(main)
{
CLEANUPBLK(test)
{
}
CLEANUPBEG(test)
{
}
CLEANUPEND(test)
}
CLEANUPBEG(main)
{
...
}
CLEANUPEND(main)
У меня их тоже можно вкладывать, но прийдется вызывать все макры с именем блока.
CLEANUPBLK(main)
{
CLEANUPBLK(test)
{
}
CLEANUPBEG(test)
{
}
CLEANUPEND(test)
}
CLEANUPBEG(main)
{
...
}
CLEANUPEND(main)
соответственно, идея в том, что при хороших макрах в духе DRY (Don't Repeat Yourself) эти LBN и N в static Inst labels[] = { NULL, ...} и CLEANUP(LB##N,N) не будут создаваться руками, а какой-то другой макрой, чтобы нельзя было сделать дурацкую ручную ошибку.
а resultN тоже организовать в массив. Тогда можно проимитировать исключения, выдать диагностику result[N] по которой видно, где что успело проинициализироваться, а где нет.
Вопервых goto может прыгнуть хот куда, а c моими инструкциями(CLEANUPALL, CLEANUPBRK, CLEANUPSEL) мы ограничены тем блоком в котором мы их вызываем. Да и при использовании вложенных блоков гораздо трудьнее допустить ошибки с именами меток.
Количество писанины растет, а выгод как-то не прибавляется %) Насчет блочной дисциплины твоих макросов - перепутать местами CLEANUPSEL мложно, так ведь? И компилятор этого не заметит, как и в случае с goto.
>1. что будет, если попадутся вложенная часть 1.?
то есть, не A.1 A.2 B.1 B.2, а A.1 B.1 B.2 A.2 ?
(B.1 перезатирает внутреннюю переменную в макре A.1)
Я основывался на этом принципе:
{
void *label <------
|
...----------------
{ |
void *label<-- |
| |
goto *label--- |
} |
goto *label -------
}
если они верен то значит должно быть все впорядке
Если они вообще кому-то и нужны, то только тебе одному, ибо ты их автор. Все остальные пользуются структурным goto для случая отдельной функции, пулом (в стиле APR, например) для стека вызовов, или конечным автоматом. А реализовать нормальные "локальные исключения" тебе не позволит ограниченность препроцессора Си.
Сначало лишние goto ипользовал чтобы компилятор не кричал что есть неиспользованные метки, а потом для чтобы компилятор предупреждал когда я забуду одно из слов (CLEANUPBLK, CLEANUPBEG, CLEANUPNEXT, CLEANUPEND)
m4 не подойдёт? Более мощный препроцессор. Код сейчас не приведу, не
помню. Но общая идея - можно сделать блок кода аргументом макроса.
Т.е., задать что-то на входе и на выходе.
Также нужно будет помнить, что нельзя внутри блока пользоваться
longjmp (или как оно там называется). А goto и return, возможно,
стоит перекрыть.
В применении это будет выглядеть примерно так:
PROTECTED_BLOCK({ код инициализации 1},
{
защищённый код уровня 1;
return; // перекрыт, чтобы выполнять раскрутку
PROTECTED_BLOCK({ код инициализации 2},
{защищённый код уровня 2},
{откат уровня 2})
},
{ откат уровня 1 })
Я так и не понял до конца, что тебе надо, но видимо проще всего здесь юзать гнутое расширение ({ })
#define WITH(res, arg, block) { res* resource=init_res(arg); if(!res) goto end; block free_res(res*); end: }
Использование:
WITH(my_res, 123,
({
int i=0;
while( i<100 ) {
WITH( res2, 456, ({ че-то еще })
... ну и так далее
}
})
)
И еще, чтобы везде ставить ; использовать известный трюк do{}while(0);