igraph 参考手册

用于使用 igraph C 库

搜索手册

第 5 章。 错误处理

1.  错误处理基础

igraph 函数可能会遇到各种问题,导致无法正常运行。用户可能提供了无效的参数,例如,当需要方阵时提供了非方阵,或者程序在需要更多内存分配时耗尽了内存等。

默认情况下,igraph 在遇到错误时会中止程序。虽然此行为对于较小的程序来说可能足够好,但在较大的项目中无疑是可以避免的。如果您的项目需要更复杂的错误处理,请继续阅读。否则,您可以安全地跳过本章的其余部分。

2.  错误处理程序

如果 igraph 遇到错误 - 向函数提供了无效的参数,或者我们耗尽了内存 - 控制权将转移到 错误处理程序 函数。

默认的错误处理程序是 igraph_error_handler_abort,它会打印一条错误消息并中止程序。

igraph_set_error_handler() 函数可用于设置 igraph_error_handler_t 类型的新错误处理程序函数;有关详细信息,请参阅此类型的文档。

还有两个预定义的错误处理程序函数,igraph_error_handler_ignoreigraph_error_handler_printignore。这些函数会释放临时分配的内存(稍后会详细介绍),并返回错误代码。后者还会打印一条错误消息。如果您使用这些错误处理程序,则需要通过检查(几乎)每个非 void igraph 函数的返回值来自己处理可能的错误。

无论安装了哪个错误处理程序,库中的所有函数都会尽最大努力在发生错误时保持其参数 语义 不变。通过语义,我们指的是作为参数提供的对象的实现可能会更改,但其在大多数情况下的 含义 不会更改。本手册中记录了极少数违反此规则的情况。

2.1. igraph_error_handler_t — 错误处理函数的类型。

typedef void igraph_error_handler_t(const char *reason, const char *file,
                                    int line, igraph_error_t igraph_errno);

这是错误处理函数的类型。

参数: 

reason:

错误的文本描述。

file:

注意到错误的源文件。

line:

触发错误的源文件中的行号

igraph_errno:

igraph 错误代码。

2.2. igraph_error_handler_abort — 在发生错误时中止程序。

IGRAPH_FUNCATTR_NORETURN igraph_error_handler_t igraph_error_handler_abort;

默认错误处理程序,打印错误消息并中止程序。

2.3. igraph_error_handler_ignore — 忽略错误。

igraph_error_handler_t igraph_error_handler_ignore;

此错误处理程序释放临时分配的内存并返回错误代码。

2.4. igraph_error_handler_printignore — 打印并忽略错误。

igraph_error_handler_t igraph_error_handler_printignore;

释放临时分配的内存,向标准错误打印错误消息并返回错误代码。

3.  错误代码

每个可能失败的 igraph 函数都会返回一个整数错误代码。有些函数非常简单,不会遇到任何错误,这些函数也可能返回其他类型或 voidigraph_error_type_t 枚举定义了错误代码。

3.1. igraph_error_t — 返回错误代码的函数的返回类型。

typedef igraph_error_type_t igraph_error_t;

此类型用作返回错误代码的 igraph 函数的返回类型。它是一个类型别名,因为 igraph_error_t 曾经是一个 int,并且使用方式与 igraph_error_type_t 略有不同。

3.2. igraph_error_type_t — 错误代码类型。

typedef enum {
    IGRAPH_SUCCESS           = 0,
    IGRAPH_FAILURE           = 1,
    IGRAPH_ENOMEM            = 2,
    IGRAPH_PARSEERROR        = 3,
    IGRAPH_EINVAL            = 4,
    IGRAPH_EXISTS            = 5,
    IGRAPH_EINVEVECTOR       = 6,
    IGRAPH_EINVVID           = 7,
    IGRAPH_NONSQUARE         = 8,
    IGRAPH_EINVMODE          = 9,
    IGRAPH_EFILE             = 10,
    IGRAPH_UNIMPLEMENTED     = 12,
    IGRAPH_INTERRUPTED       = 13,
    IGRAPH_DIVERGED          = 14,
    IGRAPH_ARPACK_PROD       = 15,   /* unused, reserved */
    IGRAPH_ARPACK_NPOS       = 16,
    IGRAPH_ARPACK_NEVNPOS    = 17,
    IGRAPH_ARPACK_NCVSMALL   = 18,
    IGRAPH_ARPACK_NONPOSI    = 19,
    IGRAPH_ARPACK_WHICHINV   = 20,
    IGRAPH_ARPACK_BMATINV    = 21,
    IGRAPH_ARPACK_WORKLSMALL = 22,
    IGRAPH_ARPACK_TRIDERR    = 23,
    IGRAPH_ARPACK_ZEROSTART  = 24,
    IGRAPH_ARPACK_MODEINV    = 25,
    IGRAPH_ARPACK_MODEBMAT   = 26,
    IGRAPH_ARPACK_ISHIFT     = 27,
    IGRAPH_ARPACK_NEVBE      = 28,
    IGRAPH_ARPACK_NOFACT     = 29,
    IGRAPH_ARPACK_FAILED     = 30,
    IGRAPH_ARPACK_HOWMNY     = 31,
    IGRAPH_ARPACK_HOWMNYS    = 32,
    IGRAPH_ARPACK_EVDIFF     = 33,
    IGRAPH_ARPACK_SHUR       = 34,
    IGRAPH_ARPACK_LAPACK     = 35,
    IGRAPH_ARPACK_UNKNOWN    = 36,
    IGRAPH_ENEGLOOP          = 37,
    IGRAPH_EINTERNAL         = 38,
    IGRAPH_ARPACK_MAXIT      = 39,
    IGRAPH_ARPACK_NOSHIFT    = 40,
    IGRAPH_ARPACK_REORDER    = 41,
    IGRAPH_EDIVZERO          = 42,
    IGRAPH_GLP_EBOUND        = 43,
    IGRAPH_GLP_EROOT         = 44,
    IGRAPH_GLP_ENOPFS        = 45,
    IGRAPH_GLP_ENODFS        = 46,
    IGRAPH_GLP_EFAIL         = 47,
    IGRAPH_GLP_EMIPGAP       = 48,
    IGRAPH_GLP_ETMLIM        = 49,
    IGRAPH_GLP_ESTOP         = 50,
    IGRAPH_EATTRIBUTES       = 51,
    IGRAPH_EATTRCOMBINE      = 52,
    IGRAPH_ELAPACK           = 53,
    IGRAPH_EDRL IGRAPH_DEPRECATED_ENUMVAL = 54,
    IGRAPH_EOVERFLOW         = 55,
    IGRAPH_EGLP              = 56,
    IGRAPH_CPUTIME           = 57,
    IGRAPH_EUNDERFLOW        = 58,
    IGRAPH_ERWSTUCK          = 59,
    IGRAPH_STOP              = 60,
    IGRAPH_ERANGE            = 61,
    IGRAPH_ENOSOL            = 62
} igraph_error_type_t;

这些是 igraph 函数可能返回的值。请注意,只有在您使用 igraph_set_error_handler() 定义了错误处理程序时,这些值才具有意义。否则,程序会中止,导致错误的函数永远不会返回。

值: 

IGRAPH_SUCCESS:

该函数已成功完成其任务。

IGRAPH_FAILURE:

出了点问题。您几乎不会遇到此错误,因为通常会使用更具体的错误代码。

IGRAPH_ENOMEM:

堆上没有足够的内存可供分配。

IGRAPH_PARSEERROR:

在文件中发现解析错误。

IGRAPH_EINVAL:

参数的值无效。例如,负数被指定为顶点数。

IGRAPH_EXISTS:

图/顶点/边属性已使用给定的名称安装。

IGRAPH_EINVEVECTOR:

无效的顶点 ID 向量。顶点 ID 为负数或大于顶点数减 1。

IGRAPH_EINVVID:

无效的顶点 ID,负数或太大。

IGRAPH_NONSQUARE:

接收到非方阵,而预期方阵。

IGRAPH_EINVMODE:

无效的模式参数。

IGRAPH_EFILE:

文件操作失败。例如,文件不存在,或者用户没有打开它的权限。

IGRAPH_UNIMPLEMENTED:

尝试调用未实现或禁用(在编译时)的函数。

IGRAPH_DIVERGED:

数值算法未能收敛。

IGRAPH_ARPACK_PROD:

矩阵向量乘积失败(不再使用)。

IGRAPH_ARPACK_NPOS:

N 必须为正数。

IGRAPH_ARPACK_NEVNPOS:

NEV 必须为正数。

IGRAPH_ARPACK_NCVSMALL:

NCV 必须更大。

IGRAPH_ARPACK_NONPOSI:

最大迭代次数应为正数。

IGRAPH_ARPACK_WHICHINV:

无效的 WHICH 参数。

IGRAPH_ARPACK_BMATINV:

无效的 BMAT 参数。

IGRAPH_ARPACK_WORKLSMALL:

WORKL 太小。

IGRAPH_ARPACK_TRIDERR:

LAPACK 在三对角矩阵特征值计算中出错。

IGRAPH_ARPACK_ZEROSTART:

起始向量为零。

IGRAPH_ARPACK_MODEINV:

MODE 无效。

IGRAPH_ARPACK_MODEBMAT:

MODE 和 BMAT 不兼容。

IGRAPH_ARPACK_ISHIFT:

ISHIFT 必须为 0 或 1。

IGRAPH_ARPACK_NEVBE:

NEV 和 WHICH='BE' 不兼容。

IGRAPH_ARPACK_NOFACT:

无法构建 Arnoldi 分解。

IGRAPH_ARPACK_FAILED:

没有足够精确的特征值。

IGRAPH_ARPACK_HOWMNY:

HOWMNY 无效。

IGRAPH_ARPACK_HOWMNYS:

未实现 HOWMNY='S'。

IGRAPH_ARPACK_EVDIFF:

收敛的 Ritz 值数量不同。

IGRAPH_ARPACK_SHUR:

从实 Schur 形式的计算中出错。

IGRAPH_ARPACK_LAPACK:

用于计算特征向量的 LAPACK (dtrevc) 错误。

IGRAPH_ARPACK_UNKNOWN:

未知的 ARPACK 错误。

IGRAPH_ENEGLOOP:

在计算最短路径时检测到负环。

IGRAPH_EINTERNAL:

内部错误,可能是 igraph 中的错误。

IGRAPH_EDIVZERO:

大整数除以零。

IGRAPH_GLP_EBOUND:

GLPK 错误 (GLP_EBOUND)。

IGRAPH_GLP_EROOT:

GLPK 错误 (GLP_EROOT)。

IGRAPH_GLP_ENOPFS:

GLPK 错误 (GLP_ENOPFS)。

IGRAPH_GLP_ENODFS:

GLPK 错误 (GLP_ENODFS)。

IGRAPH_GLP_EFAIL:

GLPK 错误 (GLP_EFAIL)。

IGRAPH_GLP_EMIPGAP:

GLPK 错误 (GLP_EMIPGAP)。

IGRAPH_GLP_ETMLIM:

GLPK 错误 (GLP_ETMLIM)。

IGRAPH_GLP_ESTOP:

GLPK 错误 (GLP_ESTOP)。

IGRAPH_EATTRIBUTES:

属性处理程序错误。用户预计不会发现此错误;如果某些 igraph 函数未正确使用属性处理程序接口,则会发出信号。

IGRAPH_EATTRCOMBINE:

给定属性类型的未实现的属性组合方法。

IGRAPH_ELAPACK:

LAPACK 调用导致错误。

IGRAPH_EDRL:

DrL 布局生成器中的内部错误;不再使用(已替换为 IGRAPH_EINTERNAL)。

IGRAPH_EOVERFLOW:

整数或双精度溢出。

IGRAPH_EGLP:

内部 GLPK 错误。

IGRAPH_CPUTIME:

超过 CPU 时间。

IGRAPH_EUNDERFLOW:

整数或双精度下溢。

IGRAPH_ERWSTUCK:

随机游走卡住。

IGRAPH_ERANGE:

超过最大顶点或边计数。

IGRAPH_ENOSOL:

输入问题无解。

3.3. igraph_strerror — 错误的文本描述。

const char *igraph_strerror(const igraph_error_t igraph_errno);

这是一个简单的实用函数,它给出了 igraph 错误代码的简短通用文本描述。

参数: 

igraph_errno:

igraph 错误代码。

返回值: 

指向错误代码的文本描述的指针。

4.  警告消息

除了错误消息之外,igraph 还支持警告消息。警告消息通常不会终止程序,但它们通常对用户至关重要。

igraph 警告的处理方式与错误类似。有一个单独的警告处理程序函数,每当 igraph 函数触发警告时都会调用该函数。此处理程序可以通过 igraph_set_warning_handler() 函数进行设置。有两个预定义的简单警告处理程序,igraph_warning_handler_ignore()igraph_warning_handler_print(),后者是默认值。

要触发警告,igraph 函数通常使用 IGRAPH_WARNING() 宏,igraph_warning() 函数,或者如果需要更大的灵活性,则使用 igraph_warningf()

4.1. igraph_warning_handler_t — igraph 警告处理函数 的类型。

typedef void igraph_warning_handler_t(const char *reason,
                                      const char *file, int line);

当前将其定义为与 igraph_error_handler_t 具有相同的类型,尽管未使用最后一个(错误代码)参数。

4.2. igraph_set_warning_handler — 安装警告处理程序。

igraph_warning_handler_t *igraph_set_warning_handler(igraph_warning_handler_t *new_handler);

安装提供的警告处理程序函数。

参数: 

new_handler:

要安装的新警告处理程序函数。在此处提供一个空指针以卸载当前的警告处理程序,而不安装新的警告处理程序。

返回值: 

当前的警告处理程序函数。

4.3. IGRAPH_WARNING — 触发警告。

#define IGRAPH_WARNING(reason)

这是从 igraph 函数触发警告的常用方法。它调用 igraph_warning()

参数: 

reason:

警告消息。

4.4. IGRAPH_WARNINGF — 触发警告,具有类似 printf 的语法。

#define IGRAPH_WARNINGF(reason, ...)

igraph 函数可以在它们注意到警告并想要向用户传递有关出错原因的额外信息时使用此宏。它使用正确的参数和无错误代码调用 igraph_warningf()

参数: 

reason:

警告的文本描述,一个模板字符串,其语法与标准 printf C 库函数相同。

...:

要替换到模板字符串中的其他参数。

4.5. igraph_warning — 报告警告。

void igraph_warning(const char *reason, const char *file, int line);

如果您想要从使用 igraph 的函数中触发警告,请调用此函数。

参数: 

reason:

警告的文本描述。

file:

注意到警告的源文件。

line:

触发警告的源文件中的行号。

igraph_errno:

警告也可能具有错误代码,但当前在 igraph 中未使用。

返回值: 

提供的错误代码。

4.6. igraph_warningf — 报告警告,类似 printf 的版本。

void igraph_warningf(const char *reason, const char *file, int line, ...);

此函数类似于 igraph_warning(),但使用类似 printf 的语法。它将其他参数替换到 reason 模板字符串中,并调用 igraph_warning()

参数: 

reason:

警告的文本描述,一个模板字符串,其语法与标准 printf C 库函数相同。

file:

注意到警告的源文件。

line:

触发警告的源文件中的行号。

igraph_errno:

警告也可能具有错误代码,但当前在 igraph 中未使用。

...:

要替换到模板字符串中的其他参数。

4.7. igraph_warning_handler_ignore — 忽略所有警告。

void igraph_warning_handler_ignore(const char *reason, const char *file, int line);

此警告处理程序函数仅忽略所有警告。

参数: 

reason:

警告的文本描述。

file:

注意到警告的源文件。

line:

触发警告的源文件中的行号。

igraph_errno:

警告也可能具有错误代码,但当前在 igraph 中未使用。

4.8. igraph_warning_handler_print — 将所有警告打印到标准错误。

void igraph_warning_handler_print(const char *reason, const char *file, int line);

此警告处理程序函数仅将所有警告打印到标准错误。

参数: 

reason:

警告的文本描述。

file:

注意到警告的源文件。

line:

触发警告的源文件中的行号。

igraph_errno:

警告也可能具有错误代码,但当前在 igraph 中未使用。

5. 高级主题

5.1.  编写错误处理程序

本章其余部分的内容可能仅对那些想要从另一种语言创建 igraph 接口或从 GUI 应用程序中使用 igraph 的人有用。大多数读者可以安全地跳到下一章。

您可以通过定义 igraph_error_handler_t 类型的函数并调用 igraph_set_error_handler() 来编写和安装错误处理程序。此功能对于接口编写者很有用,因为 igraph 将有机会以适当的方式发出错误信号。例如,R 接口使用 R 的本机打印设施来传达错误,而 Python 接口将它们转换为 Python 异常。

错误处理程序的两个主要任务是报告错误(即打印错误消息)并确保正确的资源清理。这可以通过调用 IGRAPH_FINALLY_FREE() 来确保,该函数会释放一些临时内存以避免内存泄漏。请注意,这可能会使传递给错误处理程序的错误消息缓冲区 reason 无效。在调用 IGRAPH_FINALLY_FREE() 之后,请勿访问它。

igraph 0.10 开始,通过多次调用错误处理程序(并间接调用 IGRAPH_FINALLY_FREE())分阶段释放临时内存。因此,预计不会立即中止程序的错误处理程序会返回。错误处理程序不应执行 longjmp,因为这可能导致某些内存无法释放。

5.1.1. igraph_set_error_handler — 设置新的错误处理程序。

igraph_error_handler_t *igraph_set_error_handler(igraph_error_handler_t *new_handler);

安装新的错误处理程序。如果使用 NULL 调用,它会安装默认的错误处理程序(当前为 igraph_error_handler_abort)。

参数: 

new_handler:

要安装的错误处理程序函数。

返回值: 

旧的错误处理程序函数。如果不再需要 new_handler,则应保存和还原此函数。

5.2.  错误处理内部机制

如果发生错误,库中的函数会使用错误的文本描述和 igraph 错误代码调用 IGRAPH_ERROR() 宏。此宏(通过 igraph_error() 函数)调用已安装的错误处理程序。另一个有用的宏是 IGRAPH_CHECK()。此宏检查其参数(通常是函数调用)的返回值,如果它不是 IGRAPH_SUCCESS,则调用 IGRAPH_ERROR()

5.2.1. IGRAPH_ERROR — 触发错误。

#define IGRAPH_ERROR(reason, igraph_errno)

igraph 函数通常在注意到错误时使用此宏。它使用正确的参数调用 igraph_error(),如果该函数返回,则该宏也会使用错误代码返回“调用”函数。如果出于某种(可疑的)原因您想要在不从当前函数返回的情况下调用错误处理程序,请直接调用 igraph_error()

参数: 

reason:

错误的文本描述。这应该比与错误代码关联的文本更具描述性。例如,如果错误代码是 IGRAPH_EINVAL,则其关联的文本(请参阅 igraph_strerror())为“无效值”,并且此字符串应解释哪个参数无效,以及可能的原因。

igraph_errno:

igraph 错误代码。

5.2.2. IGRAPH_ERRORF — 触发错误,具有类似 printf 的语法。

#define IGRAPH_ERRORF(reason, igraph_errno, ...)

igraph 函数可以在它们注意到错误并想要向用户传递有关出错原因的额外信息时使用此宏。它使用正确的参数调用 igraph_errorf(),如果该函数返回,则该宏也会使用错误代码返回“调用”函数。如果出于某种(可疑的)原因您想要在不从当前函数返回的情况下调用错误处理程序,请直接调用 igraph_errorf()

参数: 

reason:

错误的文本描述,一个模板字符串,其语法与标准 printf C 库函数相同。这应该比与错误代码关联的文本更具描述性。例如,如果错误代码是 IGRAPH_EINVAL,则其关联的文本(请参阅 igraph_strerror())为“无效值”,并且此字符串应解释哪个参数无效,以及可能预期的值和接收到的值。

igraph_errno:

igraph 错误代码。

...:

要替换到模板字符串中的其他参数。

5.2.3. igraph_error — 报告错误。

igraph_error_t igraph_error(const char *reason, const char *file, int line,
                            igraph_error_t igraph_errno);

igraph 函数通常在注意到错误时调用此函数(通常通过 IGRAPH_ERROR 宏)。它使用提供的参数调用当前安装的错误处理程序函数。

参数: 

reason:

错误的文本描述。

file:

注意到错误的源文件。

line:

触发错误的源文件中的行号。

igraph_errno:

igraph 错误代码。

返回值: 

错误代码(如果返回)

另请参阅: 

5.2.4. igraph_errorf — 报告错误,类似 printf 的版本。

igraph_error_t igraph_errorf(const char *reason, const char *file, int line,
                             igraph_error_t igraph_errno, ...);

参数: 

reason:

错误的文本描述,解释为 printf 格式字符串。

file:

注意到错误的源文件。

line:

触发错误的源文件中的行。

igraph_errno:

igraph 错误代码。

...:

其他参数,要替换到格式字符串中的值。

另请参阅: 

5.2.5. IGRAPH_CHECK — 检查函数调用的返回值。

#define IGRAPH_CHECK(expr)

参数: 

expr:

一个表达式,通常是一个函数调用。保证只计算一次。

执行表达式并检查其值。如果该值不是 IGRAPH_SUCCESS,则使用该值作为错误代码调用 IGRAPH_ERROR。以下是一个示例用法

 IGRAPH_CHECK(vector_push_back(&v, 100)); 

编写 igraph 函数时,仅有一个理由使用此宏。如果用户安装了一个返回到辅助调用代码的错误处理程序(如 igraph_error_handler_ignoreigraph_error_handler_printignore),并且从另一个 igraph 函数调用发出错误信号的 igraph 函数,那么我们需要确保将错误传播回辅助(即非 igraph)调用函数。这可以通过在每个可能返回错误代码的 igraph 调用上使用 IGRAPH_CHECK 来实现。

5.2.6. IGRAPH_CHECK_CALLBACK — 检查回调的返回值。

#define IGRAPH_CHECK_CALLBACK(expr, code)

IGRAPH_CHECK 相同,但将 IGRAPH_STOP 视为正常的(非错误的)返回代码。此宏在某些 igraph 函数中使用,这些函数允许用户通过回调函数挂接到长时间运行的计算中。当用户定义的回调函数返回 IGRAPH_SUCCESS 时,计算将正常进行。从回调返回 IGRAPH_STOP 将终止计算,而不会报告错误。从回调返回任何其他值都将被视为错误代码,并且 igraph 将在退出函数之前触发必要的清理函数。

请注意,IGRAPH_CHECK_CALLBACK 不以任何方式处理 IGRAPH_STOP,除了在 code 指向的变量中返回它。调用方有责任相应地处理 IGRAPH_STOP

参数: 

expr:

一个表达式,通常是对用户定义的回调函数的调用。保证只计算一次。

code:

指向 igraph_error_t 类型的可选变量的指针;如果此变量的值不是空指针,则会将其设置为错误代码。

5.3.  释放内存

如果函数遇到错误(且程序未中止),则错误处理程序应释放所有临时内存。这是通过将所有临时对象的地址和销毁函数存储在堆栈中来完成的。IGRAPH_FINALLY 函数通过将其地址放入堆栈中来声明一个对象为临时对象。如果 igraph 函数成功返回,它将调用 IGRAPH_FINALLY_CLEAN(),并指定要从堆栈中移除的对象数量。但是,如果发生错误,错误处理程序应调用 IGRAPH_FINALLY_FREE() 以释放添加到堆栈中的每个对象。这意味着在调用函数(等等)中分配的临时对象也将被释放。

5.3.1. IGRAPH_FINALLY — 注册对象以进行释放。

#define IGRAPH_FINALLY(func, ptr)

此宏将对象的地址及其析构函数的地址一起放置在堆栈上。如果发生错误,此堆栈用于释放临时分配的对象,以防止内存泄漏。手动释放后,使用 IGRAPH_FINALLY_CLEAN() 从堆栈中移除对象。

典型的用法是在初始化之后

IGRAPH_CHECK(igraph_vector_init(&vector, 0));
IGRAPH_FINALLY(igraph_vector_destroy, &vector);

最常用的数据结构(例如 igraph_vector_t)具有相关的便捷宏,可以一步初始化对象并在堆栈上注册它。因此,上面的模式可以用一行代码替换

IGRAPH_VECTOR_INIT_FINALLY(&vector, 0);

参数: 

func:

通常用于销毁对象的函数。

ptr:

指向对象本身的指针。

5.3.2. IGRAPH_FINALLY_CLEAN — 发出对象干净释放的信号。

void IGRAPH_FINALLY_CLEAN(int num);

从临时分配对象的堆栈中移除指定数量的对象。它通常在手动销毁对象后立即调用

igraph_vector_t vector;
igraph_vector_init(&vector, 10);
IGRAPH_FINALLY(igraph_vector_destroy, &vector);
// use vector
igraph_vector_destroy(&vector);
IGRAPH_FINALLY_CLEAN(1);

参数: 

num:

要从簿记堆栈中移除的对象数量。

5.3.3. IGRAPH_FINALLY_FREE — 释放当前级别注册的对象。

void IGRAPH_FINALLY_FREE(void);

调用当前级别的临时分配对象堆栈中所有对象的销毁函数,即直到 IGRAPH_FINALLY_ENTER() 设置的最近标记。此函数只能从错误处理程序中调用。使用它来代替销毁函数的每个不需要的对象是合适的,因为它也销毁调用函数的临时对象(等等)。

5.4. 编写具有适当错误处理的 igraph 函数

要使函数在错误情况下表现良好,需要遵循一些简单的规则。首先,检查函数的参数,如果它们无效,则调用 IGRAPH_ERROR()。其次,在每个动态分配的对象上调用 IGRAPH_FINALLY,并在返回之前使用适当的参数调用 IGRAPH_FINALLY_CLEAN()。第三,对所有可能生成错误的 igraph 函数调用使用 IGRAPH_CHECK

用于此簿记的堆栈大小是固定的,而且很小。如果要分配多个对象,请编写一个可以释放所有这些对象的销毁函数。有关示例,请参见 igraph 源代码中的 adjlist.c 文件。

对于某些函数,这些机制根本不够灵活。这些函数应定义自己的错误处理程序,并在返回之前恢复错误处理程序。

示例 5.1. 文件 examples/simple/igraph_contract_vertices.c

#include <igraph.h>

/* Create the condensation of a directed graph.
 * See https://en.wikipedia.org/wiki/Strongly_connected_component#Definitions
 * This example demonstrates how to write a basic igraph function, complete
 * with error handling. */
igraph_error_t condensation(const igraph_t *graph, igraph_t *cond) {
    igraph_vector_int_t membership;

    /* Data structures such as vector must be initialized in igraph before use. */
    IGRAPH_CHECK(igraph_vector_int_init(&membership, 0));

    /* Adding the initialized vector to the "finally" stack ensures that it will
     * be automatically destroyed if an error occurs. */
    IGRAPH_FINALLY(igraph_vector_int_destroy, &membership);

    /* Functions that return an error code can be wrapped in IGRAPH_CHECK to pass that error
     * up to the caller. */
    IGRAPH_CHECK(igraph_connected_components(graph, &membership, /* csize */ NULL, /* no */ NULL, IGRAPH_STRONG));

    /* To compute the condensation, we simply contract strongly connected components.
     * Since igraph_contract_vertices() modifies graphs in-place, we make a copy first. */
    IGRAPH_CHECK(igraph_copy(cond, graph));

    /* Since we are not done creating the condensation yet, we add 'cond' to the
     * "finally" stack, so that it will be destroyed if an error occurs. */
    IGRAPH_FINALLY(igraph_destroy, cond);

    /* Contract strongly connected components. */
    IGRAPH_CHECK(igraph_contract_vertices(cond, &membership, NULL));

    /* igraph_contract_vertices() preserves all edges, some of which become
     * parallel edges or self-loops after the contraction. We simplify these. */
    IGRAPH_CHECK(igraph_simplify(cond, /* remove_multiple */ true, /* remove_loops */ true, NULL));

    /* Data structures that are no longer needed must be explicitly destroyed.
     * If they were added to the "finally" stack, they must be removed explicitly,
     * in the opposite order to how they were added. IGRAPH_FINALLY_CLEAN removes
     * the indicated number of entries from the "finally" stack. We remove
     * 'membership' because it was destroyed, and 'cond' because the responsibility
     * to destroy it is now with the caller. */
    igraph_vector_int_destroy(&membership);
    IGRAPH_FINALLY_CLEAN(2);

    return IGRAPH_SUCCESS; /* return with no error */
}

int main(void) {
    igraph_t graph, cond;

    /* Create a random directed graph with mean degree 2 and compute its condensation. */
    igraph_erdos_renyi_game_gnm(&graph, 100, 200, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS);
    condensation(&graph, &cond);

    printf("Number of vertices in the condensation: %" IGRAPH_PRId "\n", igraph_vcount(&cond));
    igraph_write_graph_edgelist(&cond, stdout);

    /* Destroy data structures that are no longer needed. */
    igraph_destroy(&graph);
    igraph_destroy(&cond);

    return 0;
}


5.5. 致命错误

在某些极少数情况下,igraph 可能会遇到无法完全处理的内部错误。在这种情况下,它将调用当前的致命错误处理程序。默认的致命错误处理程序只是打印错误并中止程序。

致命错误处理程序不返回。通常,它们可能会立即中止程序,或者在高级 igraph 接口的情况下,它们可能会使用 longjmp() 返回到顶层。仅当发生严重错误时才调用致命错误处理程序,因此 igraph 可能处于不一致的状态。返回到顶层的目的是给用户一个保存工作的机会,而不是立即中止。但是,程序会话应尽快重新启动。

大多数使用 igraph 的项目都将使用默认的致命错误处理程序。

5.5.1. igraph_fatal_handler_t — igraph 致命错误处理函数类型。

typedef void igraph_fatal_handler_t(const char *reason, const char *file, int line);

此类型的函数必须不返回。通常,它们会调用 abort() 或执行 longjmp()

参数: 

reason:

错误的文本描述。

file:

注意到错误的源文件。

line:

触发错误的源文件中的行号。

5.5.2. igraph_set_fatal_handler — 安装致命错误处理程序。

igraph_fatal_handler_t *igraph_set_fatal_handler(igraph_fatal_handler_t *new_handler);

安装提供的致命错误处理函数。

致命错误处理函数必须不返回。通常,致命错误处理程序会调用 abort()longjmp()

参数: 

new_handler:

要安装的新的致命错误处理函数。在此处提供一个空指针以卸载当前的致命错误处理程序,而不安装新的。

返回值: 

当前的致命错误处理函数。

5.5.3. igraph_fatal_handler_abort — 在发生致命错误时中止程序。

IGRAPH_FUNCATTR_NORETURN igraph_fatal_handler_t igraph_fatal_handler_abort;

默认的致命错误处理程序,打印错误消息并中止程序。

5.5.4. IGRAPH_FATAL — 触发致命错误。

#define IGRAPH_FATAL(reason)

这是从 igraph 函数触发致命错误的常用方法。它调用 igraph_fatal()

仅在无法处理错误的情况下使用此宏。处理错误的常用方法是 IGRAPH_ERROR()

参数: 

reason:

错误消息。

5.5.5. IGRAPH_FATALF — 触发致命错误,具有类似 printf 的语法。

#define IGRAPH_FATALF(reason, ...)

当发生致命错误并且想要向用户传递有关出错原因的额外信息时,igraph 函数可以使用此宏。它使用适当的参数调用 igraph_fatalf()

参数: 

reason:

错误的文本描述,一个模板字符串,其语法与标准 printf C 库函数相同。

...:

要替换到模板字符串中的其他参数。

5.5.6. IGRAPH_ASSERTassert() 的 igraph 特定替代品。

#define IGRAPH_ASSERT(condition)

此宏类似于标准 assert(),但是它不是调用 abort(),而是调用 igraph_fatal()。这允许将控制权返回给调用程序,例如,返回到高级 igraph 接口中的顶层。

assert() 不同,当定义 NDEBUG 宏时,IGRAPH_ASSERT() 不会被禁用。

此宏旨在供 igraph 内部使用。

由于典型的致命错误处理程序执行 longjmp(),因此避免在 C++ 代码中使用此宏。对于大多数编译器,当 longjmp() 离开当前作用域时,不会调用析构函数。

参数: 

condition:

要检查的条件。

5.5.7. igraph_fatal — 触发致命错误。

void igraph_fatal(const char *reason, const char *file, int line);

此函数触发致命错误。通常,它是通过 IGRAPH_FATAL()IGRAPH_ASSERT() 间接调用的。

参数: 

reason:

错误的文本描述。

file:

注意到错误的源文件。

line:

触发错误的源文件中的行号。

5.5.8. igraph_fatalf — 触发致命错误,类似 printf 的语法。

void igraph_fatalf(const char *reason, const char *file, int line, ...);

此函数类似于 igraph_fatal(),但使用类似 printf 的语法。它将附加参数替换为 reason 模板字符串,并调用 igraph_fatal()

参数: 

reason:

错误的文本描述。

file:

注意到错误的源文件。

line:

触发错误的源文件中的行号。

...:

要替换到模板字符串中的其他参数。

5.6. 错误处理和线程

igraph 错误处理方法很可能不是线程安全的,主要是因为用于存储临时分配对象地址的静态全局堆栈。此问题可能会在 igraph 的更高版本中得到解决。