处理来自多个 fread 调用的错误的更简洁的方法

Neater way to handle errors from multiple fread calls

我在 C 中有以下方法来加载二进制文件,看起来相当冗长乏味,必须检查每个 fread 调用的错误值,有没有更简洁的方法来处理这个问题?

我知道可以通过一次读取结构来减少一些调用,但是由于 C 可以在结构成员之间添加填充字节,我宁愿避免这种情况。

some_type_t *load_something(FILE *file) {
    some_type_t *something = (some_type_t *)malloc(sizeof(some_type_t));
    if (something == NULL) {
        return NULL;
    }

    if (fread(&something->field1, sizeof(something->field1), 1, file) == 0) {
        free(something);
        return NULL;
    }
    if (fread(&something->field2, sizeof(something->field2), 1, file) == 0) {
        free(something);
        return NULL;
    }
    if (fread(&something->field3, sizeof(something->field3), 1, file) == 0) {
        free(something);
        return NULL;
    }

    uint16_t some_var1, some_var2, some_var3;

    some_other_type_t *something_else1 = (some_other_type_t *)malloc(sizeof(some_other_type_t));
    if (fread(&some_var1, sizeof(some_var1), 1, file) == 0) {
        free(something);
        free(something_else1);
        return NULL;
    }

    some_other_type_t *something_else2 = (some_other_type_t *)malloc(sizeof(some_other_type_t));
    if (fread(&some_var2, sizeof(some_var2), 1, file) == 0) {
        free(something);
        free(something_else1);
        free(something_else2);
        return NULL;
    }

    some_other_type_t *something_else3 = (some_other_type_t *)malloc(sizeof(some_other_type_t));
    if (fread(&some_var3, sizeof(some_var3), 1, file) == 0) {
        free(something);
        free(something_else1);
        free(something_else2);
        free(something_else3);
        return NULL;
    }
    // Do something with the vars and allocated something elses.
    // ...

    return something;
}

为什么不创建宏:

#define READ_FIELD(data) \
    do { if (fread(&data, sizeof(data), 1, file) == 0) { \
        free(something); \
        free(something_else1);
        free(something_else2);
        return NULL; \
    } } while(0)

然后像函数调用一样调用它:

READ_FIELD(something->field1);
READ_FIELD(something->field2);

READ_FIELD(some_var1);
READ_FIELD(some_var2);

代码将是相同的,但至少它现在是生成的而不是 copied/pasted(可能有错误)。

宏必须在所有可能的内存块上调用 free,即使是那些尚未分配的内存块。唯一的限制是将未分配的设置为 NULL,这样 free 就不会崩溃。并且要超级安全地更改为:

free(something); something = NULL;

(当然,如果 something 是分配指针的副本,设置为 NULL 并不能防止双重释放,它有限制)

您也可以将此技术应用于写入端,并且由于 M Oehm 建议,您可以在包装器宏中列出您想要 read/write 的内容:

#define DO_ALL \
    DO_FIELD(something->field1); \
    DO_FIELD(something->field2); \
    DO_FIELD(some_var1); \
    DO_FIELD(some_var2)

然后将 DO_FIELD 定义为 READ_FIELDWRITE_FIELD:

#define DO_FIELD READ_FIELD
DO_ALL;
#undef DO_FIELD

没有什么可以让您省去检查每个调用是否成功,但您可以使用 goto 改进代码结构(这实际上是 goto 在C, 伪代码如下):

    if (first_call() < 0) goto error;
    if (second_call() < 0) goto error;

    // [...]
    // when everything succeeded:
    return result;

error:
    // free resources
    // return error-indicator, e.g.
    return 0;

如果要释放的资源在您的函数运行过程中累积,请确保首先将它们全部初始化为 NULL/0(假设它们是指针)。然后,在 error 部分中对它们调用 free() 在它们尚未分配时无效。如果您使用自己的 "destructor functions",请确保以与 free() 相同的方式设计它们——当传递 NULL 值时,它应该是空操作。

您需要将所有 something 指针初始化为 NULL 并将清理集中在一个地方。

    ...
    something = something_else1 = something_else2 = something_else3 = NULL;
    ...
    some_other_type_t *something_else3 = (some_other_type_t *)malloc(sizeof(some_other_type_t));
    if (fread(&some_var3, sizeof(some_var3), 1, file) == 0) {
      goto error;
    }
    // Do something with the vars and allocated something elses.
    // ...

    return something;

  error:
    free(something);
    free(something_else1);
    free(something_else2);
    free(something_else3);
    return NULL;
  }

释放一个 NULL 指针是可以的,它什么都不做,因此你不需要在调用 free 之前检查 someting 指针是否是 NULL

旁注:在 C 中,您不会转换 malloc 的 return 值。

这里有一个简单的方法来将 mallocfread 操作分组并检查一次是否正确完成:

some_type_t *load_something(FILE *file) {
    uint16_t some_var1, some_var2, some_var3;
    some_type_t *something = malloc(sizeof(*something));
    some_other_type_t *something_else1 = malloc(sizeof(*something_else1));
    some_other_type_t *something_else2 = malloc(sizeof(*something_else2));
    some_other_type_t *something_else3 = malloc(sizeof(*something_else3));

    if (!something || !something_else1 || !something_else2 || !something_else3 ||
        !fread(&something->field1, sizeof(something->field1), 1, file) ||
        !fread(&something->field2, sizeof(something->field2), 1, file) ||
        !fread(&something->field3, sizeof(something->field3), 1, file) ||
        !fread(&some_var1, sizeof(some_var1), 1, file) ||
        !fread(&some_var2, sizeof(some_var2), 1, file) ||
        !fread(&some_var3, sizeof(some_var3), 1, file))
    {
        free(something);
        free(something_else1);
        free(something_else2);
        free(something_else3);
        return NULL;
    }

    // Do something with the vars and allocated something elses.
    // ...

    return something;
}

请注意,将空指针传递给 free() 是可以的。