在C中,被调用的函数可以使调用方函数返回



我想找到一种方法,让函数返回(带有特定值)调用它的函数。这在C中可能吗?也许通过检查调用堆栈?

摘要示例:假设我们有两个函数

int called() {
    if (some_check_fails()) {
        /* here make caller() return -1 so "Hello world!n" is not printed */
    }
}
int caller() {
    called();
    printf("Hello world!n");
    return 0;
}

我正在找一些东西放在/* ... */部分。

现实生活中的例子:我正在处理的代码是一个在SQLite文件中导出数据的函数。这意味着对SQLite API的大量调用需要每次检查返回值。结果是一个看起来非常好看而且太长的函数,比如if (resp_code != SQLITE_OK)部分不断重复:

sqlite3 *db;
char *err_msg;
/* Open the database in read-write mode, create it if not exists yet */
int resp_code = sqlite3_open_v2(filename, &db,
                                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
                                NULL);
if (resp_code != SQLITE_OK) {
    fprintf(stderr, "SQLite error: cannot open database %s, %sn", filename,
            sqlite3_errmsg(db));
    sqlite3_close(db);
    return OMG_ERROR_HERE;
}
/* Create table */
char *query_table = "DROP TABLE IF EXISTS sometable; "
                    "CREATE TABLE sometable "
                    "(value int, data TEXT);";
resp_code = sqlite3_exec(db, query_table, 0, 0, &err_msg);
if (resp_code != SQLITE_OK) {
    fprintf(stderr, "SQLite error: %sn", err_msg);
    sqlite3_free(err_msg);
    sqlite3_close(db);
    return OMG_ERROR_HERE;
}
/* Prepare statement */
char *query = "INSERT INTO sometable VALUES (@i, @s);";
sqlite3_stmt *stmt;
resp_code = sqlite3_prepare_v2(db, query, 150, &stmt, NULL);
if (resp_code != SQLITE_OK) {
    fprintf(stderr, "SQLite error: %sn", err_msg);
    sqlite3_free(err_msg);
    sqlite3_close(db);
    return OMG_ERROR_HERE;
}
/* Start transaction */
resp_code = sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, &err_msg);
if (resp_code != SQLITE_OK) {
    fprintf(stderr, "SQLite error: %sn", err_msg);
    sqlite3_free(err_msg);
    sqlite3_close(db);
    return OMG_ERROR_HERE;
}
/* AND SO ON */

我想要的是:

sqlite3 *db;
char *err_msg;
/* Open the database in read-write mode, create it if not exists yet */
int resp_code = sqlite3_open_v2(filename, &db,
                                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
                                NULL);
return_this_function_if_not_ok(resp_code);
/* Create table */
char *query_table = "DROP TABLE IF EXISTS sometable; "
                    "CREATE TABLE sometable "
                    "(value int, data TEXT);";
resp_code = sqlite3_exec(db, query_table, 0, 0, &err_msg);
return_this_function_if_not_ok(resp_code);
/* Prepare statement */
char *query = "INSERT INTO sometable VALUES (@i, @s);";
sqlite3_stmt *stmt;
resp_code = sqlite3_prepare_v2(db, query, 150, &stmt, NULL);
return_this_function_if_not_ok(resp_code);
/* Start transaction */
resp_code = sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, &err_msg);
return_this_function_if_not_ok(resp_code);
/* AND SO ON */

编辑2015-12-21:我选择FUZxxl的答案是最好的,因为它是唯一一个真正回答我关于返回调用者函数的问题的答案。另一方面,chux的答案(基于Rowland Shaw和Ziffusion的)是以我喜欢的方式解决我的SQLite问题。

非常感谢大家

这样的事情在C中是不可能的,但你可以接近它。

标识符__func__在每个函数的开头被隐式声明为

static const char __func__[];

它的值是作为字符串的当前函数的名称。您可以编写一个类似函数的宏,该宏隐式地将调用者的名称传递给被调用者。如果应该接收调用方名称的函数类似于:

void error_check_fun(const char *function, int code, int result);

你可以这样写一个宏:

#define error_check(code, result) error_check_fun(__func__, code, result);

类似地,__FILE____LINE__是分别扩展到当前源文件和行的宏。

您可以尝试这样的方法。

#define return_this_function_if_not_ok(db, sql_code, sql_msg, code) 
    if ((sql_code) != SQLITE_OK) { 
        fprintf(stderr, "SQLite error: %sn", (*sql_msg)); 
        sqlite3_free(sql_msg); 
        sqlite3_close(db); 
        return (code); 
    }
sqlite3 *db;
char *err_msg;
/* Open the database in read-write mode, create it if not exists yet */
int resp_code = sqlite3_open_v2(filename, &db,
                                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
                                &err_msg);
return_this_function_if_not_ok(db, resp_code, err_msg, OMG_ERROR_HERE);

推荐像Rowland Shaw和@Ziffusion 这样的东西

调用一个函数来打包数据并处理常见的内务管理。

int foo(char *err_msg, int code) {
  if (msg) {
    fprintf(stderr, "SQLite error: %sn", err_msg);
    sqlite3_free(err_msg);
  } else {
    fprintf(stderr, "SQLite error: %sn", "Default error message");
  }
  sqlite3_close(db);
  return code;
}
resp_code = sqlite3_exec(...);
if (resp_code != SQLITE_OK) return foo(err_msg, OMG_ERROR_HERE);
...
resp_code = sqlite3_prepare_v2(db, query, 150, &stmt, NULL);
if (resp_code != SQLITE_OK) return foo(NULL, OMG_ERROR_HERE);

进一步建议,包括文件和行号。这是我发现非常有用的东西。

int bar(char *err_msg, int code, const char *file, int line) {
  fprintf(stderr, "SQLite error:%s, Code:%d, File:%s, Line%dn",
     err_msg ? err_msg : "Default error message", code, file, line);
  }
  sqlite3_free(err_msg);
  sqlite3_close(db);
  return code;
}
#define foo(err_msg, code, file, line) bar((err_msg), (code), __FILE__, __LINE__)

您可以使用setjmp()/longjmp()来获得这种效果,尽管与您想要的不完全一样:

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

int check_result;
int some_check_fails() {
    return check_result;
}
int called(jmp_buf buf) {
    if (some_check_fails()) {
        longjmp(buf,1);
    }
}
int caller() {
    jmp_buf buf;
    if (setjmp(buf)==0) {
        called(buf);
        printf("Hello world!n");
        return 0;
    }
    else {
        printf("Failuren");
        return -1;
    }
}
int main() {
    check_result = 0;
    caller();
    check_result = 1;
    caller();
}

输出:

你好,世界!失败

这种技术确实避免了将检查放在多个位置,从而有效地实现了一种类型的异常处理机制。然而,还有其他方法可以在不使用此方法的情况下清理代码。

最新更新