Call Stack Management
In order to obtain the traceback in run time, we need to actively manage the call stack. Unfortunately, C doesn’t provide an easy way to do this. Therefore, we will have to wrap function calls or code blocks with a macro. While this approach can be quite verbose, it is what makes our library possible.
break, continue, return and goto.API
ctb_clear_context Function
Clear the C Traceback context (including errors and call stack).
void ctb_clear_context(void);TRACE Macro
Wrapper macro for expression to automatically manage call stack frames without checking for errors.
void TRACE(expr)Always use TRY instead of TRACE when error handling matters.
It is easy to miss errors as TRACE are not checking for errors stored
in context.
Parameters
| Name | Type | Description |
|---|---|---|
expr | N/A | The expression to be traced. |
Expands to
do \
{ \
ctb_push_call_stack_frame(__FILE__, __func__, __LINE__, #expr); \
(expr); \
ctb_pop_call_stack_frame(__FILE__, __func__, __LINE__, #expr); \
} while (0)Usage
int function_a(void)
{
TRACE(function_b()); // Trace a function call
}
int function_b(void)
{
int i = 0;
TRACE(i = 2); // Trace any expression
}TRACE_BLOCK Macro
Wrapper macro for a code block to automatically manage call stack frames without checking for errors.
void TRACE_BLOCK(...)Always call ctb_check_error after TRACE_BLOCK when error handling matters.
It is easy to miss errors as TRACE_BLOCK are not checking for errors stored
in context.
Parameters
| Name | Type | Description |
|---|---|---|
... | N/A | The block of code to be traced. |
Expands to
do \
{ \
ctb_push_call_stack_frame(__FILE__, __func__, __LINE__, #__VA_ARGS__); \
__VA_ARGS__ \
ctb_pop_call_stack_frame(__FILE__, __func__, __LINE__, #__VA_ARGS__); \
} while (0)Usage
int function_a(void)
{
/* Trace a code block */
TRACE_BLOCK(
int *ptr;
printf("%d", *ptr); // Oops, segmentation fault
); // <-- Don't forget the semi-colon
// Note: i is not accessible here
// printf("%d", i); <-- Illegal!
}TRY Macro
Wrapper macro for expression to automatically manage call stack frames and check for errors.
bool TRY(expr)Parameters
| Name | Type | Description |
|---|---|---|
expr | N/A | The expression to be traced. |
Returns
| Name | Type | Description |
|---|---|---|
success | bool | Whether the expression executed without error. |
Expands to
(ctb_push_call_stack_frame(__FILE__, __func__, __LINE__, #expr), \
(expr), \
ctb_pop_call_stack_frame(__FILE__, __func__, __LINE__, #expr), \
!ctb_check_error())Usage
int function_a(void)
{
bool success = TRY(function_b()); // Try a function call
}
int function_b(void)
{
int i = 0;
(void) TRY(i = 2); // Try any expression
}TRY_GOTO Macro
Wrapper for an expression. If an error occurs after the expression, jump to label.
void TRY_GOTO(expr, label)Parameters
| Name | Type | Description |
|---|---|---|
expr | N/A | The expression to be traced. |
label | N/A | The label to jump to on error. |
Expands to
do \
{ \
ctb_push_call_stack_frame(__FILE__, __func__, __LINE__, #expr); \
(expr); \
ctb_pop_call_stack_frame(__FILE__, __func__, __LINE__, #expr); \
if (ctb_check_error()) \
{ \
goto label; \
} \
} while (0)Usage
int function_a(void)
{
TRY_GOTO(function_b(), error); // Trace a function call
/* Do something */
return 0;
error:
return 1;
}TRY_BLOCK_GOTO Macro
Wrapper for a block of code. If an error occurs after the block executes, jump to label.
void TRY_BLOCK_GOTO(label, ...)Parameters
| Name | Type | Description |
|---|---|---|
label | N/A | The label to jump to on error. |
... | N/A | The block of code to be traced. |
Expands to
do \
{ \
ctb_push_call_stack_frame(__FILE__, __func__, __LINE__, #__VA_ARGS__); \
__VA_ARGS__ \
ctb_pop_call_stack_frame(__FILE__, __func__, __LINE__, #__VA_ARGS__); \
if (ctb_check_error()) \
{ \
goto label; \
} \
} while (0)Usage
int function_a(void)
{
/* Trace a code block */
TRY_BLOCK_GOTO(
error,
int *ptr;
printf("%d", *ptr); // Oops, segmentation fault
); // <-- Don't forget the semi-colon
// Note: i is not accessible here
// printf("%d", i); <-- Illegal!
return 0;
error:
return 1;
}