Я думал сегодня о блоках try/catch, существующих на других языках. Некоторое время искал это, но без результата. Из того, что я знаю, в C. не существует такой вещи, как try/catch. Однако есть ли способ имитировать их?
Конечно, есть утвердительные и другие трюки, но ничего похожего на try/catch, которые также вызывают повышенное исключение. Спасибо вам
Попробовать команды catch в C
Ответ 1
Сам C не поддерживает исключения, но вы можете имитировать их до некоторой степени с помощью вызовов setjmp
и longjmp
.
static jmp_buf s_jumpBuffer;
void Example() {
if (setjmp(s_jumpBuffer)) {
// The longjmp was executed and returned control here
printf("Exception happened here\n");
} else {
// Normal code execution starts here
Test();
}
}
void Test() {
// Rough equivalent of 'throw'
longjmp(s_jumpBuffer, 42);
}
На этом сайте есть хорошее руководство по моделированию исключений с помощью setjmp
и longjmp
Ответ 2
Вы используете goto в C для подобных ситуаций обработки ошибок.
Это самый близкий эквивалент исключений, которые вы можете получить в C.
Ответ 3
В C99 вы можете использовать setjmp
/longjmp
для нелокального потока управления.
В рамках одной области общий шаблон структурированного кодирования для C при наличии множественных распределений ресурсов и нескольких выходов использует goto
, например в этом примере. Это похоже на то, как С++ реализует деструкторные вызовы автоматических объектов под капотом, и если вы внимательно это выполняете, это должно позволить вам определенную степень чистоты даже в сложных функциях.
Ответ 4
Хорошо, я не мог удержаться от ответа на это. Позвольте мне сначала сказать, что я не думаю, что это хорошая идея, чтобы имитировать это в C, поскольку это действительно иностранная концепция C.
Мы можем использовать злоупотреблять препроцессором и локальными переменными стека, чтобы использовать ограниченную версию С++ try/throw/catch.
Версия 1 (локальные всплывающие окна)
#include <stdbool.h>
#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
Версия 1 - это только локальный бросок (не может покидать область действия). Он полагается на способность C99 объявлять переменные в коде (он должен работать на C89, если попытка в первую очередь входит в функцию).
Эта функция просто создает локальную переменную, поэтому она знает, была ли ошибка, и использует goto для перехода к блоку catch.
Например:
#include <stdio.h>
#include <stdbool.h>
#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
int main(void)
{
try
{
printf("One\n");
throw();
printf("Two\n");
}
catch(...)
{
printf("Error\n");
}
return 0;
}
Это работает примерно так:
int main(void)
{
bool HadError=false;
{
printf("One\n");
HadError=true;
goto ExitJmp;
printf("Two\n");
}
ExitJmp:
if(HadError)
{
printf("Error\n");
}
return 0;
}
Версия 2 (переключение области)
#include <stdbool.h>
#include <setjmp.h>
jmp_buf *g__ActiveBuf;
#define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else
#define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown)
#define throw(x) longjmp(*g__ActiveBuf,1);
Версия 2 намного сложнее, но в основном работает одинаково. Он использует длинный переход из текущей функции в блок try. Затем блок try использует if/else, чтобы пропустить блок кода в блок catch, который проверяет локальный переменная, чтобы увидеть, следует ли ее улавливать.
Пример снова развернут:
jmp_buf *g_ActiveBuf;
int main(void)
{
jmp_buf LocalJmpBuff;
jmp_buf *OldActiveBuf=g_ActiveBuf;
bool WasThrown=false;
g_ActiveBuf=&LocalJmpBuff;
if(setjmp(LocalJmpBuff))
{
WasThrown=true;
}
else
{
printf("One\n");
longjmp(*g_ActiveBuf,1);
printf("Two\n");
}
g_ActiveBuf=OldActiveBuf;
if(WasThrown)
{
printf("Error\n");
}
return 0;
}
В этом случае используется глобальный указатель, поэтому longjmp() знает, какая попытка была последней.
Мы используем, злоупотребляя стеком, поэтому дочерние функции также могут иметь блок try/catch.
Использование этого кода имеет несколько нижних сторон (но это забавное умственное упражнение):
- Он не будет освобождать выделенную память, так как нет вызываемых деконструкторов.
- У вас не может быть более одного try/catch в области (без вложенности)
- На самом деле вы не можете генерировать исключения или другие данные, как в С++
- Небезопасный поток
- Вы настраиваете других программистов на отказ, потому что они, вероятно, не заметят взлома и попробуют использовать их, как блоки try/catch С++.
Ответ 5
В то время как некоторые другие ответы охватывают простые случаи с использованием setjmp
и longjmp
, в реальном приложении есть две проблемы, которые действительно имеют значение.
- Вложение блоков try/catch. Использование одной глобальной переменной для вашего
jmp_buf
сделает их неработоспособными. - Threading. Единая глобальная переменная для вас
jmp_buf
вызовет все виды боли в этой ситуации.
Решением к ним является поддержка потока-локального стека jmp_buf
, который обновляется по мере продвижения. (Я думаю, что это то, что использует lua внутри).
Итак, вместо этого (от JaredPar удивительный ответ)
static jmp_buf s_jumpBuffer;
void Example() {
if (setjmp(s_jumpBuffer)) {
// The longjmp was executed and returned control here
printf("Exception happened\n");
} else {
// Normal code execution starts here
Test();
}
}
void Test() {
// Rough equivalent of `throw`
longjump(s_jumpBuffer, 42);
}
Вы бы использовали что-то вроде:
#define MAX_EXCEPTION_DEPTH 10;
struct exception_state {
jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
int current_depth;
};
int try_point(struct exception_state * state) {
if(current_depth==MAX_EXCEPTION_DEPTH) {
abort();
}
int ok = setjmp(state->jumpBuffer[state->current_depth]);
if(ok) {
state->current_depth++;
} else {
//We've had an exception update the stack.
state->current_depth--;
}
return ok;
}
void throw_exception(struct exception_state * state) {
longjump(state->current_depth-1,1);
}
void catch_point(struct exception_state * state) {
state->current_depth--;
}
void end_try_point(struct exception_state * state) {
state->current_depth--;
}
__thread struct exception_state g_exception_state;
void Example() {
if (try_point(&g_exception_state)) {
catch_point(&g_exception_state);
printf("Exception happened\n");
} else {
// Normal code execution starts here
Test();
end_try_point(&g_exception_state);
}
}
void Test() {
// Rough equivalent of `throw`
throw_exception(g_exception_state);
}
Снова более реалистичная версия этого будет включать некоторый способ хранения информации об ошибках в exception_state
, более эффективную обработку MAX_EXCEPTION_DEPTH
(возможно, использование realloc для создания буфера или что-то в этом роде).
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: вышеуказанный код был написан без каких-либо испытаний. Это чисто потому, что вы получаете представление о том, как структурировать вещи. В разных системах и разных компиляторах необходимо будет реализовать локальное хранилище потоков по-разному. Код, вероятно, содержит как ошибки компиляции, так и логические ошибки, поэтому, когда вы можете использовать его по своему усмотрению, ПРОВЕРЬТЕ его перед его использованием;)
Ответ 6
Это можно сделать с помощью setjmp/longjmp
в C. P99 имеет довольно удобный набор инструментов для этого, который также согласуется с новым нить модели C11.
Ответ 7
Быстрый поиск в google дает решения kludgey, такие как this, которые используют setjmp/longjmp, как упомянули другие. Ничего такого простого и элегантного, как С++/Java try/catch. Я довольно частично отношусь к обработке исключений Ada.
Проверить все с помощью операторов if:)
Ответ 8
Предупреждение: следующее не очень приятно, но оно выполняет эту работу.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
unsigned int id;
char *name;
char *msg;
} error;
#define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " ‘%s_error’ " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__)
#define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__)
#define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg)
#define _errordef(n, _id) \
error* new_##n##_error_msg(char* msg) { \
error* self = malloc(sizeof(error)); \
self->id = _id; \
self->name = #n; \
self->msg = msg; \
return self; \
} \
error* new_##n##_error() { return new_##n##_error_msg(""); }
#define errordef(n) _errordef(n, __COUNTER__ +1)
#define try(try_block, err, err_name, catch_block) { \
error * err_name = NULL; \
error ** __err = & err_name; \
void __try_fn() try_block \
__try_fn(); \
void __catch_fn() { \
if (err_name == NULL) return; \
unsigned int __##err_name##_id = new_##err##_error()->id; \
if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \
printuncaughterr(); \
else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \
catch_block \
} \
__catch_fn(); \
}
#define throw(e) { *__err = e; return; }
_errordef(any, 0)
Использование:
errordef(my_err1)
errordef(my_err2)
try ({
printf("Helloo\n");
throw(new_my_err1_error_msg("hiiiii!"));
printf("This will not be printed!\n");
}, /*catch*/ any, e, {
printf("My lovely error: %s %s\n", e->name, e->msg);
})
printf("\n");
try ({
printf("Helloo\n");
throw(new_my_err2_error_msg("my msg!"));
printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
printerr("%s", e->msg);
})
printf("\n");
try ({
printf("Helloo\n");
throw(new_my_err1_error());
printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
printf("Catch %s if you can!\n", e->name);
})
Вывод:
Helloo
My lovely error: my_err1 hiiiii!
Helloo
/home/naheel/Desktop/aa.c:28: error: ‘my_err2_error’ my msg!
Helloo
/home/naheel/Desktop/aa.c:38: uncaught error: ‘my_err1_error’
Имейте в виду, что это использование вложенных функций и __COUNTER__
. Вы будете в безопасности, если используете gcc.
Ответ 9
Это еще один способ обработки ошибок в C, который более эффективен, чем использование setjmp/longjmp. К сожалению, он не будет работать с MSVC, но если использовать только GCC/Clang - это вариант, вы можете его рассмотреть. В частности, он использует расширение "label as value", которое позволяет вам взять адрес метки, сохранить ее в значении и безоговорочно перейти к ней. Я приведу его, используя пример:
GameEngine *CreateGameEngine(GameEngineParams const *params)
{
/* Declare an error handler variable. This will hold the address
to jump to if an error occurs to cleanup pending resources.
Initialize it to the err label which simply returns an
error value (NULL in this example). The && operator resolves to
the address of the label err */
void *eh = &&err;
/* Try the allocation */
GameEngine *engine = malloc(sizeof *engine);
if (!engine)
goto *eh; /* this is essentially your "throw" */
/* Now make sure that if we throw from this point on, the memory
gets deallocated. As a convention you could name the label "undo_"
followed by the operation to rollback. */
eh = &&undo_malloc;
/* Now carry on with the initialization. */
engine->window = OpenWindow(...);
if (!engine->window)
goto *eh; /* The neat trick about using approach is that you don't
need to remember what "undo" label to go to in code.
Simply go to *eh. */
eh = &&undo_window_open;
/* etc */
/* Everything went well, just return the device. */
return device;
/* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
Если вы так хотите, вы можете реорганизовать общий код в определении, эффективно внедряя свою собственную систему обработки ошибок.
/* Put at the beginning of a function that may fail. */
#define declthrows void *_eh = &&err
/* Cleans up resources and returns error result. */
#define throw goto *_eh
/* Sets a new undo checkpoint. */
#define undo(label) _eh = &&undo_##label
/* Throws if [condition] evaluates to false. */
#define check(condition) if (!(condition)) throw
/* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */
#define checkpoint(label, condition) { check(condition); undo(label); }
Тогда пример станет
GameEngine *CreateGameEngine(GameEngineParams const *params)
{
declthrows;
/* Try the allocation */
GameEngine *engine = malloc(sizeof *engine);
checkpoint(malloc, engine);
/* Now carry on with the initialization. */
engine->window = OpenWindow(...);
checkpoint(window_open, engine->window);
/* etc */
/* Everything went well, just return the device. */
return device;
/* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}
Ответ 10
Redis использует goto для имитации try/catch, ИМХО это очень чисто и элегантно:
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */
int rdbSave(char *filename) {
char tmpfile[256];
FILE *fp;
rio rdb;
int error = 0;
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
strerror(errno));
return REDIS_ERR;
}
rioInitWithFile(&rdb,fp);
if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
errno = error;
goto werr;
}
/* Make sure data will not remain on the OS output buffers */
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
if (rename(tmpfile,filename) == -1) {
redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
unlink(tmpfile);
return REDIS_ERR;
}
redisLog(REDIS_NOTICE,"DB saved on disk");
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = REDIS_OK;
return REDIS_OK;
werr:
fclose(fp);
unlink(tmpfile);
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
return REDIS_ERR;
}
Ответ 11
В C вы можете "моделировать" исключения вместе с автоматическим "восстановлением объекта" посредством ручного использования if + goto для явной обработки ошибок.
Я часто пишу код на C следующим образом (сводится к тому, чтобы выделить обработку ошибок):
#include <assert.h>
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
if ( ( ret = foo_init( f ) ) )
goto FAIL;
if ( ( ret = goo_init( g ) ) )
goto FAIL_F;
if ( ( ret = poo_init( p ) ) )
goto FAIL_G;
if ( ( ret = loo_init( l ) ) )
goto FAIL_P;
assert( 0 == ret );
goto END;
/* error handling and return */
/* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
Это полностью стандарт ANSI C, отделяющий обработку ошибок от вашего основного кода, позволяет (вручную) раскручивать в стеке инициализированные объекты, как это делает C++, и совершенно очевидно, что здесь происходит. Поскольку вы явно тестируете на наличие ошибок в каждой точке, это облегчает вставку определенной регистрации или обработки ошибок в каждом месте, где может возникнуть ошибка.
Если вы не возражаете против небольшой магии макросов, вы можете сделать это более кратким, делая другие вещи, такие как регистрация ошибок со следами стека. Например:
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, ret, strerror( ret ) ); goto LABEL; } while ( 0 )
typedef int errcode;
errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
errcode ret = 0;
TRY( ret = foo_init( f ), FAIL );
TRY( ret = goo_init( g ), FAIL_F );
TRY( ret = poo_init( p ), FAIL_G );
TRY( ret = loo_init( l ), FAIL_P );
assert( 0 == ret );
goto END;
/* error handling and return */
FAIL_P:
poo_fini( p );
FAIL_G:
goo_fini( g );
FAIL_F:
foo_fini( f );
FAIL:
assert( 0 != ret );
END:
return ret;
}
Конечно, это не так элегантно, как C++ исключения + деструкторы. Например, вложение нескольких стеков обработки ошибок в одну функцию не очень чистое. Вместо этого вы, вероятно, захотите разбить их на автономные подфункции, которые аналогичным образом обрабатывают ошибки, инициализируйте + финализируйте явно, как это.
Это также работает только в пределах одной функции и не будет продолжать подниматься вверх по стеку, если вызывающие абоненты более высокого уровня не реализуют подобную явную логику обработки ошибок, тогда как исключение C++ будет просто продолжать подниматься вверх по стеку, пока не найдет подходящий обработчик. Это также не позволяет вам генерировать произвольный тип, но вместо этого только код ошибки.
Систематическое кодирование таким способом (т.е. С одной входной и одной выходной точкой) также позволяет очень легко вставлять логику "до и после" ("наконец"), которая будет выполняться независимо от того, что. Вы просто помещаете свою логику "наконец-то" после метки END.
Ответ 12
Если вы используете C с Win32, вы можете использовать Structured Exception Handling (SEH) для имитации try/catch.
Если вы используете C на платформах, которые не поддерживают setjmp()
и longjmp()
, посмотрите на Обработка исключений библиотеки pjsip, он обеспечивает собственную реализацию
Ответ 13
Возможно, не основной язык (к сожалению), но в APL, это операция ⎕EA (обозначить Execute Alternate).
Использование: 'Y' ⎕EA 'X' где X и Y являются фрагментами кода, представленными в виде строк или имен функций.
Если X попадает в ошибку, вместо этого выполняется Y (обычно обработка ошибок).