在C中命名宏有限制/问题吗?

时间:2014-10-06 13:14:42

标签: c macros naming

我想拥有可以包含可选参数的函数。当然这不能用C来完成,但它可能有一些宏观的魔力:

#define _macroWith1Arg(_0, _1, macroName, ...)          _ ## macroName
#define _macroWith2Args(_0, _1, _2, macroName, ...)     _ ## macroName
#define _macroWith3Args(_0, _1, _2, _3, macroName, ...) _ ## macroName

#define macroWith1Arg(macroName, ...)               _macroWith1Arg(_0, __VA_ARGS__, macroName ## _1, macroName ## _0)(__VA_ARGS__)
#define macroWith2Args(macroName, ...)              _macroWith2Args(_0, __VA_ARGS__, macroName ## _2, macroName ## _1, macroName ## _0)(__VA_ARGS__)
#define macroWith3Args(macroName, ...)              _macroWith3Args(_0, __VA_ARGS__, macroName ## _3, macroName ## _2, macroName ## _1, macroName ## _0)(__VA_ARGS__)

#define _sum_1(_1)      (_1)
#define _sum_2(_1, _2)  (_1) + (_2)
#define sum(...)        macroWith2Args(sum, __VA_ARGS__)

fprintf(stderr, "%d ", sum(1)); // Prints 1
fprintf(stderr, "%d ", sum(1, 2)); // Prints 3

我喜欢它看起来的样子:它看起来像带有可选参数的普通函数。但缺点是你不能有像sum这样的宏的函数指针。所以我听说你应该把这个名字大写,把它显式地标记为一个宏(比如“SUM”),以避免混淆。

现在我的问题是:这个命名约定是否真的有必要,我的意思是标准C用errno作为例子(至少在我的平台上)?我认为大多数IDE都可以识别宏并将其突出显示。我真的很想听听你对此的看法。

修改

我找到了一种方法来实现你可以使用函数指针:

int my_sum(int, int);
#define _sum_1(_1)     my_sum(_1, 0)
#define _sum_2(_1, _2) my_sum(_1, _2)
#define sum(...)       macroWith2Args(sum, __VA_ARGS__)
int (*sum)(int, int) = my_sum;

// Use it as a "function":
fprintf(stderr, "%d ", sum(1, 2)); // Prints 3
// Take a function pointer:
int (*sumptr)(int, int) = sum;
fprintf(stderr, "%d ", sumptr(1, 0)); // Prints 1

4 个答案:

答案 0 :(得分:5)

在C中(预处理器是其中的一部分),您可以在编辑中执行更多操作:宏和函数可以具有相同的名称。虽然在应用程序代码中很少这样做,但C库本身可以拥有它。这里标准函数可以作为宏实现,但也必须提供具有相同名称的符号。

因此标准本身并不遵守宏名称应全部为大写的规则。

我认为如果你在实施中小心,

  • 使用一整套参数调用宏只是预处理器阶段中的标识
  • 有效的函数声明提供了相同的名称

然后使用小写标识符应该没有问题。

由此确保

  • 重复您的功能原型仍然是一个有效的操作
  • 获取函数指针可以毫无问题地完成

并且在您的所有实现中都表现为就好像您的宏所在的函数。

顺便说一下,您发布的代码不符合标准。以下划线开头的标识符保留在文件范围中。这不是惯例,而是必要的。

答案 1 :(得分:4)

命名约定通常不是必要。它们的存在是为了向其他程序员传达信息(包括将来几周/几个月)。当另一个程序员看到

sum(a, b, c, d);

他们会立即认为这是一个需要4个参数的正常功能。但是,如果他们看到

SUM(a, b, c, d);

然后他们会知道它是一个宏(假设他们熟悉C命名约定)。毕竟,您并不总是在IDE中查看知道您已定义的宏和功能的文件。人们将在diff工具和文本编辑器中看到您的代码。他们通常会期望所有大写字母中的宏都不是宏以外的所有大写字母。

答案 2 :(得分:2)

约定在C中工作较少,因为C工具较少,编译器工作较少,程序员必须灵活地实现某些目的。说宏的约定是大写+下划线,但如果你想让人们认为某些宏是函数,那么你最好不遵循这个约定。实际上,有些库假定不遵循这个约定而在struct中伪造一个字段。

你提到了errno。这是一个很好的例子。它本质上是一个宏(glibc),返回一个左值,所以你可以写errno = 0;。所以通过这种方式,图书馆提供商让你认为errno是一个全局变量(这曾经是我认为的那样。),但事实并非如此。 C程序员倾向于灵活并且发挥一些合理的技巧。它与Java或C#不同,这种面向企业的语言需要程序员严格遵循惯例。

答案 3 :(得分:0)

// notice that the handling of a variable argument list
// is simple, built into C, and does not require a bunch of
// home grown macros.

// this function will take the number of values to average
// followed by all of the numbers to average

double average ( int num, ... )
{
    va_list arguments;                // A place to store the list of arguments
    double sum = 0;

    va_start ( arguments, num );  // Initializing arguments to store all values after num
    for ( int x = 0; x < num; x++ )   // Loop until all numbers are added
    {
        sum += va_arg ( arguments, double ); // Adds the next value in argument list to sum.
    }
    va_end ( arguments );                  // Cleans up the list

    return sum / num;                      // Returns the average
}