这是宏观滥用吗?

时间:2010-09-02 18:51:54

标签: c file-io macros coding-style

我正在对一些代码进行逆向工程并遇到了这个......

/************************************************************************/
/*                                                                      */
/*                          MACRO CHECK_FREAD                           */
/*                                                                      */
/*  CHECK_FREAD is used to check the status of a file read.  It         */
/*   is passed the return code from read and a string to print out if   */
/*   an error is detected.  If an error is found, an error message is   */
/*   printed out and the program terminates.  This was made into a      */
/*   macro because it had to be done over and over and over . . .       */
/*                                                                      */
/************************************************************************/

#define CHECK_FREAD(X, msg)  if (X==-1) \
                 { \
                return(DCD_BADREAD); \
                 }

基本上,只要这个函数执行(c-read)检查结果是否有错误,该文件中的特定函数集就会调用它。他们也有一个类似的EOF检查...基本上我可以告诉他们,他们这样做是为了在一堆地方的函数中间插入错误返回。

e.g。

/*  Read in an 4                */
    ret_val = read(fd, &input_integer, sizeof(int32));

    CHECK_FREAD(ret_val, "reading an 4");
    CHECK_FEOF(ret_val, "reading an 4");

    if (input_integer != 4)
    {
        return(DCD_BADFORMAT);
    }

    /*  Read in the number of atoms         */
    ret_val = read(fd, &input_integer, sizeof(int32));
    *N = input_integer;

    CHECK_FREAD(ret_val, "reading number of atoms");
    CHECK_FEOF(ret_val, "reading number of atoms");

现在我刚刚回到c / c ++编程中,我从来没有使用过宏,但是这个宏滥用了吗?

我看了这个...... When are C++ macros beneficial?

......它听起来不像任何犹太示例,所以我的猜测是“是”。但我想得到一个更明智的意见,而不仅仅是做出有根据的猜测...... :)

...错误,使用某种包装不是更好吗?

8 个答案:

答案 0 :(得分:22)

因为它不一样。如果你把同一个东西放到一个函数中,你将有一个短函数,如果DCD_BADWRITE等于-1,则返回X。但是,有问题的宏会从包含函数返回。例如:

int foobar(int value) {
    CHECK_FREAD(value, "Whatever");
    return 0;
}

将扩展为:

int foobar(int value) {
    if (value == -1) {
        return (DCD_BADWRITE);
    };
    return 0;
}

但是,如果它是一个函数,外部函数(foobar)会很乐意忽略结果并返回0。

宏看起来非常像一个函数,但在关键方面表现不同,是一个主要的禁忌IMO。我会说是的,宏滥用。

答案 1 :(得分:8)

这不能作为函数完成,因为return从调用函数返回,而不是宏中的块。

这是一种给宏提供错误名称的东西,因为它隐藏了一个重要的控制逻辑。

答案 2 :(得分:5)

关于是否要召唤这样一个宏*滥用*我已经开始了,但我会说这不是一个好主意。

我认为一般来说,你应该避免在宏中使用return。很难创建一个正确处理返回的宏,而不会使调用它的函数的控制流变得笨拙或难以理解。你绝对不想在没有意识到的情况下“意外”从函数返回。那可能是调试烦人的。

另外,这个宏可能会导致代码流问题,具体取决于它的使用方式。例如,代码

if (some_condition)
    CHECK_FREAD(val, "string")
else
    something_else();

不会按照您的想象执行(else与宏内的if关联)。宏需要包含在{ }中,以确保它不会改变任何周围的条件。

此外,未使用第二个参数。这里显而易见的问题是实现与文档不匹配。隐藏的问题是当像这样调用宏时:

char* messages[4];          // Array of output messages
char* pmsg = &messages[0];
....
CHECK_FREAD(val, pmsg++);

在调用宏之后,您希望指针移动到下一个数组元素。但是,不使用第二个参数,因此增量永远不会发生。这是带有副作用的语句导致宏出现问题的另一个原因。

为什么不编写一个函数来封装read和错误检查,而不是使用宏来完成所有这些操作?它可以在成功或错误代码时返回零。如果错误处理代码在所有read块之间是通用的,您可以执行以下操作:

int your_function(whatever) {
    int ret;
    ...

    if ((ret=read_helper(fd, &input_integer)) != 0)
        goto error_case;

    ...

    // Normal return from the function
    return 0;

error_case:
    print_error_message_for_code(ret);
    return ret;
}

只要您对read_helper的所有调用都将其返回值分配给ret,就可以在整个函数中重复使用相同的错误处理代码块。您的函数print_error_message_for_code只会将错误代码作为输入,并使用switch或数组打印出与其对应的错误消息。

我承认很多人都害怕goto,但重复使用常见的错误处理代码而不是一系列嵌套的块和条件变量可能是一个合适的用例。只需保持清洁和可读(每个函数一个标签,并保持错误处理代码简单)。

答案 3 :(得分:2)

这是否是滥用是一个品味问题。但是我看到了它的一些主要问题

  1. 文档完全正确 错
  2. 实施非常危险 因为if而且可能 悬空else问题
  3. 命名并不暗示 是初步回归的 周围的功能
  4. 所以风格确实非常糟糕。

答案 4 :(得分:2)

如果宏位于源文件中并且仅在该文件中使用,那么我发现它比在某个标题中某个地方关闭时更具攻击性。但是我不太喜欢返回的宏,(特别是记录来终止应用程序并实际返回的那个),而且还有一个有条件返回的宏,因为它使得创建非常容易以下内存泄漏:

char *buf = malloc(1000);
if (buf == 0) return ENOMEM;

ret_val = read(fd, buf, 1000);
CHECK_READ(ret_val, "buffer read failed")

// do something with buf

free(buf);

如果我相信文档,我没有理由怀疑这个代码在读取失败时会泄漏内存。即使文档是正确的,我也更喜欢C语言中的控制流明显,这意味着不会被宏隐藏。我还希望我的代码在我有资源清理的情况和我没有的情况之间看起来模糊一致,而不是将宏用于一个而不是另一个。因此宏观不符合我的口味,即使它不是明确的滥用。

要做文档所说的内容,我宁愿写一些类似的东西:

check(retval != -1, "buffer read failed");

check是一个函数。或者使用assert。当然,如果未设置NDEBUG,断言只会执行任何操作,因此如果您计划单独的调试和发布版本,则对错误处理没有好处。

要执行宏实际执行的操作,我宁愿完全跳过宏,并写下:

if (retval == -1) return DCD_BADREAD;

这几乎不是一长串代码,并且在共同编写不同的实例时没有真正的好处。通过封装/隐藏现有的,记录良好的read表示错误的方法,这可能也不是一个好主意,这是C程序员广泛理解的。如果没有别的,这个检查完全忽略了read产生的数据少于你要求的事实,没有可观察到的原因。这不是宏的直接错误,但宏背后的整个想法似乎来自错误的错误检查。

可能这里发生的事情是他们曾经有一个终止的宏,然后他们决定他们不想在发生故障时立即中止程序,而是他们想要返回这个错误代码DCD_BADREAD。他们应该做的是完全删除宏并更改所有调用站点 - 如果更改函数的控制逻辑,并更改它返回的内容,最好明显更改该函数的源。他们所做的是做出最小的改变,给出他们想要的结果。当时这似乎是一个好主意(忘记更新评论可悲的是一个常见的错误)。但是,正如每个人似乎都同意的那样,它导致了非常狡猾的代码。

控制流的宏可以非滥用,但我认为只有在它们看起来像控制流的情况下才能准确记录。西蒙·塔特姆的共同例程宏出现在脑海中 - 有些人首先不喜欢共同惯例,但假设你接受这个前提,一个名为crReturn的宏至少看起来像一样它会回归。

答案 5 :(得分:1)

我会考虑示例宏滥用有两个原因:(1)宏的名称并不清楚它的作用,最明显的是它可能会返回; (2)宏在语法上不等同于语句。代码如

  if (someCondition)
    CHECK_FREAD(whatever);
  else
    do_something_else();

会失败。我的首选改变:

#define LOG_AND_RET_ERROR(msg) do {LOG_ERROR(msg); return DCD_BADREAD;} while(0)
  if (x==-1) LOG_AND_RET_ERROR(msg);

答案 6 :(得分:0)

好吧,如果你定义一个简短的函数,那么你必须在每个调用站点输入更多的字符。即。

CHECK_FREAD(X, msg)

VS

if (check_fread(X, msg)) return DCD_BADREAD;

答案 7 :(得分:0)

在它实现的空间范围内,它可能没问题(尽管在C ++中通常不赞成)。

令我担心的最令人眼花缭乱的事情是,一些新介绍的开发人员可能会看到并想到这一点

if (CHECK_FREAD(...)) {
   /* do stuff here */
} else {
   /* error handle */
}

这显然是错误的。

此外,似乎msg未被使用,除非在DCD_BADREAD消耗/预期,这使情况恶化......