Ir1Ka / blogs

Apache License 2.0
0 stars 0 forks source link

在 C 语言中实现 try-catch #1

Open Ir1Ka opened 3 years ago

Ir1Ka commented 3 years ago

在 C 语言中实现 try-catch

C 语言中提供了 setjmplongjmp来实现跨函数无条件跳转。

include/ctry-catch.h

#include <stdio.h>
#include <stdbool.h>
#include <setjmp.h>

extern __thread void *try_envp;
void init_try_envp(void);

void try_depth_inc(void);
void try_depth_dec(void);
int try_depth_get(void);

#define try_fprintf(fp, fmt, ...)   \
    fprintf(fp, "%s %s().L%d: " fmt, __FILE__, __func__, __LINE__, __VA_ARGS__)
#define try_printf(fmt, ...)        \
    try_fprintf(stdout, fmt, __VA_ARGS__)

#ifdef CTRY_DEBUG
#define endtry_fprintf(fp, fmt, ...)    \
    try_fprintf(fp, "[%d] " fmt, try_depth_get(), __VA_ARGS__)
#else
#define endtry_fprintf try_fprintf
#endif /* CTRY_DEBUG */

#define try                             \
    do {                                \
        int catch_val;                  \
        jmp_buf try_env;                \
        void *try_envp_save;            \
        init_try_envp();                \
        try_envp_save = try_envp;       \
        try_envp = try_env;             \
        try_depth_inc();                \
        catch_val = setjmp(try_envp);   \
        switch (catch_val) {            \
        case 0:

#define catch(x)                        \
        break;                          \
        case x:                         \
            try_envp = try_envp_save;   \
            try_depth_dec();            \

#define endtry                                      \
        break;                                      \
        default:                                    \
            try_envp = try_envp_save;               \
            try_depth_dec();                        \
            endtry_fprintf(stderr,                  \
                           "Unknown exception %d, " \
                           "%s ...\n",              \
                           catch_val,               \
                           try_depth_get() ? "throwing" : "exiting");   \
            if (try_depth_get()) throw(catch_val);  \
            else exit(catch_val);                   \
        }                                           \
        if (!catch_val) {                           \
            try_envp = try_envp_save;               \
            try_depth_dec();                        \
        }                                           \
    } while(0);

#define throw(x) longjmp(try_envp, x)

ctry-catch.c

#include <ctry-catch.h>

static __thread jmp_buf try_env = {};
__thread void *try_envp = NULL;
static __thread int try_depth = 0;

void init_try_envp(void)
{
    if (!try_envp)
        try_envp = try_env;
}

void try_depth_inc(void)
{
    try_depth++;
}

void try_depth_dec(void)
{
    try_depth--;
}

int try_depth_get(void)
{
    return try_depth;
}

main.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <ctry-catch.h>

static void usage(char *prog)
{
    printf("%s: [EXCEPTION0 [EXCEPTION1]] [OPTION ...]\n", prog);
    printf("  [EXCEPTION0]: A intagae number\n");
    printf("  [EXCEPTION1]: A intagae number\n");
    printf("  -h          : Show this messages\n");
}

static void test_try_catch(int exception0, int exception1)
{
    try {
        printf("[%d] try\n", try_depth_get() - 1);

        try {
            printf("[%d] try\n", try_depth_get() - 1);

            if (exception0) {
                printf("throw %d\n", exception0);
                throw(exception0);
            }

        } catch(3) {
            printf("[%d] catch %d\n", try_depth_get(), 3);
            if (exception1) {
                printf("[%d] throw %d\n", try_depth_get(), exception1);
                throw(exception1);
            }
        } endtry

    } catch(1) {
        fprintf(stderr, "[%d] catch %d\n", try_depth_get(), 1);
        exit(1);
    } endtry
}

int main(int argc, char *argv[])
{
    int __argc = argc;
    char **__argv = argv;
    int exception0 = 0, exception1 = 0;

    if (argc > 1 && !strcmp("-h", argv[1])) {
        usage(argv[0]);
        exit(EXIT_SUCCESS);
    }

    if (__argc > 3) {
        fprintf(stderr, "too many parameters\n");
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }

    if (__argc > 1) {
        exception0 = strtol(__argv[1], NULL, 0);
        __argc--;
        __argv++;
    }

    if (__argc > 1) {
        exception1 = strtol(__argv[1], NULL, 0);
        __argc--;
        __argv++;
    }

    printf("exception0 %d exception1 %d\n", exception0, exception1);

    try {
        printf("[%d] try\n", try_depth_get() - 1);

        test_try_catch(exception0, exception1);

    } endtry

    printf("exit normal\n");

    return 0;
}

Makefile

APP_NAME := ctry-catch
LIB_NAME := lib$(APP_NAME).so

all: $(APP_NAME) $(LIB_NAME)

CFLAGS := -Werror -Wall
CFLAGS += -g
CFLAGS += -Iinclude
CFLAGS += -fPIC

ifneq (,$(DEBUG))
CFLAGS += -DCTRY_DEBUG
endif

LDFLAGS := -L.

$(LIB_NAME): ctry-catch.o
    $(CROSS_COMPILE)gcc -shared $(LDFLAGS) -o $@ $(filter %.o,$^)

$(APP_NAME): main.o $(LIB_NAME)
    $(CROSS_COMPILE)gcc $(LDFLAGS) -o $@ \
        $(patsubst lib%.a,-l%,$(patsubst lib%.so,-l%,$(filter lib%.so lib%.a %.o,$^)))

%.o: %.c
    $(CROSS_COMPILE)gcc $(CFLAGS) -c -o $@ $<

clean:
    rm -rf *.o $(APP_NAME) $(LIB_NAME)

ARG0 ?= 0
ARG1 ?= 0

run: all
    LD_LIBRARY_PATH=`pwd`:$${LIBRARY_PATH} ./$(APP_NAME) $(ARG0) $(ARG1) || true

help:
    @echo "make"
    @echo "make all"
    @echo "make run [ARG0=<int> [ARG1=<int>]]"
    @echo "make help"
    @echo "make clean"
Ir1Ka commented 3 years ago

暂无法实现 finally 语句块。 要实现 finally 语句可以,应该需要重新定义 return 关键字或使用替换宏。

Ir1Ka commented 3 years ago

测试环境

$ uname -srvmpio
Linux 5.10.4-1-default #1 SMP Wed Dec 30 13:10:15 UTC 2020 (4169c1f) aarch64 aarch64 aarch64 GNU/Linux

$ gcc --version
gcc (SUSE Linux) 10.2.1 20201202 [revision e563687cf9d3d1278f45aaebd03e0f66531076c9]
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat /etc/os-release
NAME="openSUSE Tumbleweed"
# VERSION="20210108"
ID="opensuse-tumbleweed"
ID_LIKE="opensuse suse"
VERSION_ID="20210108"
PRETTY_NAME="openSUSE Tumbleweed"
ANSI_COLOR="0;32"
CPE_NAME="cpe:/o:opensuse:tumbleweed:20210108"
BUG_REPORT_URL="https://bugs.opensuse.org"
HOME_URL="https://www.opensuse.org/"
DOCUMENTATION_URL="https://en.opensuse.org/Portal:Tumbleweed"
LOGO="distributor-logo"

编译:

$ make DEBUG=1
gcc -Werror -Wall -g -Iinclude -fPIC -DCTRY_DEBUG -c -o main.o main.c
gcc -Werror -Wall -g -Iinclude -fPIC -DCTRY_DEBUG -c -o ctry-catch.o ctry-catch.c
gcc -shared -L. -o libctry-catch.so ctry-catch.o
gcc -L. -o ctry-catch \
    main.o -lctry-catch

测试结果:

$ LD_LIBRARY_PATH=`pwd`:${LIBRARY_PATH} ./ctry-catch 3 1
exception0 3 exception1 1
[0] try
[1] try
[2] try
throw 3
[2] catch 3
[2] throw 1
[1] catch 1

$ echo $?
1
$ LD_LIBRARY_PATH=`pwd`:${LIBRARY_PATH} ./ctry-catch 3 2
exception0 3 exception1 2
[0] try
[1] try
[2] try
throw 3
[2] catch 3
[2] throw 2
main.c test_try_catch().L39: [1] Unknown exception 2, throwing ...
main.c main().L78: [0] Unknown exception 2, exiting ...

$ echo $?
2
$ LD_LIBRARY_PATH=`pwd`:${LIBRARY_PATH} ./ctry-catch 5
exception0 5 exception1 0
[0] try
[1] try
[2] try
throw 5
main.c test_try_catch().L34: [2] Unknown exception 5, throwing ...
main.c test_try_catch().L39: [1] Unknown exception 5, throwing ...
main.c main().L78: [0] Unknown exception 5, exiting ...

$ echo $?
5
$ LD_LIBRARY_PATH=`pwd`:${LIBRARY_PATH} ./ctry-catch
exception0 0 exception1 0
[0] try
[1] try
[2] try
exit normal

$ echo $?
0