写函数原型的明智方法

时间:2014-06-10 13:55:53

标签: c dry

我正在寻找一种(干净的)编写函数定义和函数原型的方法,而不需要代码重复。由于DRY是一个好主意,并且头文件中的手动编码原型是一个明显的违规,这似乎是一个合理的要求。

下面的示例代码表示使用预处理器解决问题的(粗略)方法。它似乎不太可能是最佳的,但似乎确实可以正常工作。

使用单独的文件和复制:

foo.h:
#ifndef FOO_H
  #define FOO_H
  // Normal header file stuff
  int dofoo(int a);
#endif /* FOO_H */

foo.c:
#include "foo.h"
int dofoo(int a) {
  return a * 2;
}

使用C预处理器:

foo.h:
#ifndef FOO_H
  #define FOO_H

  // Normal header file stuff

  #ifdef PROTOTYPE // if incorrect:
  // No consequences for this test case, but we lose a sanity check
    #error "PROTOTYPE set elsewhere, include mechanism will fall over"
  #endif

  #define PROTOTYPE // if incorrect:
  // "error: redefinition of 'dofoo'" in clang & gcc, 
  // referring to int dofoo() line in foo.c
    #include "foo.c"
  #undef PROTOTYPE //if incorrect:
  // No warnings, but should trigger the earlier #error statement if
  // this method is used in more than one file

#endif /* FOO_H */

foo.c:
#include "foo.h"

int dofoo (int a)
#ifdef PROTOTYPE // if incorrect:
// "error: redefinition of 'dofoo'" in clang & gcc, 
// referring to int dofoo() line in foo.c
  ;
#else
  {
    return a * 2;
  }
#endif

机制有点奇怪 - .h文件通常不包含.c文件!包含守卫会停止递归。它通过独立的预处理器运行时编译干净,看起来合理。否则,在整个源中嵌入预处理器条件看起来不太好。

我可以想到几种替代方法。

  1. 不要担心代码重复
  2. 更改为自动生成界面的语言
  3. 使用代码生成器(例如sqlite的makeheaders)
  4. 代码生成器可以工作,但似乎有点过分作为轻微烦恼的解决方案。由于C已经存在超过25年的某个地方,因此希望社区就最佳路径达成共识。

    感谢您的阅读。

    编辑:使用gcc 4.8.2和clang 5.1编译警告 弄乱宏语句会产生相当一致的编译器错误消息。缺少#endif(如果函数定义很长,很容易完成)会产生“error:unterminated #else”或“error:unterminated conditional directive”,两者都指向#ifdef行。

    缺少#else意味着代码不再有效C. gcc“错误:期望标识符或'('''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ,但都没有暗示#else缺失。

    如果结果是致命的,拼写PROTOTYPE错误会生成连贯的消息,如果结果无关紧要则不会发出警告。编译器警告并不像定义和声明不同时那样具体,但它们可能足够具体。

2 个答案:

答案 0 :(得分:5)

普遍接受的路径是你的选择1),不用担心,只需写两次声明。

与功能实现相比,来自原型的重复只是一小部分。像你的问题中的宏观黑客很快变得笨拙而且收效甚微。宏观机器的代码与最初的原型一样多,只是现在更难理解发生了什么,并且你会得到更加神秘的错误信息。理解重复的微不足道被大约相同数量的难以理解的技巧所取代。

使用普通原型时,编译器会在事情不匹配时发出警告,如果您忘记了#endif或其他事情,那么您会很难理解错误的宏基解决方案配对。例如,在错误中提及foo.c时,可能会定义PROTOTYPE或不定义{。}}。

答案 1 :(得分:1)

我想从另一个角度来看待它。由于我喜欢看DRY原理,它对于提供逻辑的代码是有意义的,而不是将其视为重复字符串。

这样它就不会触及声明,因为它们没有引入逻辑。当你看到几段代码时,(如执行某些任务)相同,只是参数改变,那么应该避免/重构。

这就是你实际做的事情。您刚刚在代码中引入了一些新的预处理逻辑,即#ifdef PROTOTYPE... #else ... #endif,您将反复重复原型和正文。如果你把它包装成不强制重复分支的东西,我说它有点好。

但是目前你确实在代码中重复了一些逻辑,只是为了消除多个声明,这在你提供的上下文中基本上是无害的。如果你忘了某些东西,编译器会告诉你一些不匹配的东西。它是c。

我说你提议的做法违反了它,而不是重复声明。