Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/cocoa/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C语言中的宏与函数_C_Function_C Preprocessor - Fatal编程技术网

C语言中的宏与函数

C语言中的宏与函数,c,function,c-preprocessor,C,Function,C Preprocessor,我经常看到使用宏比使用函数更好的例子 有人能举例说明宏与函数相比的缺点吗?没有重复参数和代码的类型检查,这会导致代码膨胀。宏语法还可能导致许多奇怪的边缘情况,其中分号或优先顺序可能会造成阻碍。下面的链接演示了一些宏示例1: 鉴于: int square(int x) { return x * x; } int main() { int x = 2; int y = square(x++); // fine return 0; } 例2: 与之相比: struct foo {

我经常看到使用宏比使用函数更好的例子


有人能举例说明宏与函数相比的缺点吗?

没有重复参数和代码的类型检查,这会导致代码膨胀。宏语法还可能导致许多奇怪的边缘情况,其中分号或优先顺序可能会造成阻碍。下面的链接演示了一些宏

示例1: 鉴于:

int square(int x) {
  return x * x;
}

int main() {
  int x = 2;
  int y = square(x++); // fine
  return 0;
}
例2: 与之相比:

struct foo {
  int bar;
};

int get_bar(struct foo *f) {
  return f->bar;
}

int main() {
  struct foo f;
  int a = get_bar(&f); // fine
  int b = get_bar(&a); // error, but compiler complains about passing int* where 
                       // struct foo* should be given
  return 0;
}

副作用很大。以下是一个典型的例子:

#define min(a, b) (a < b ? a : b)

min(x++, y)
它们需要在每行末尾添加一个
\


宏无法“返回”任何内容,除非将其设置为单个表达式:

#define foo(a,b,c)  \
    a += 10;        \
    b += 10;        \
    c += 10;
int foo(int *a, int *b){
    side_effect0();
    side_effect1();
    return a[0] + b[0];
}
除非使用GCC的expression语句,否则无法在宏中执行此操作。(编辑:虽然可以使用逗号运算符……但忽略了这一点……但其可读性可能仍然较低。)


操作顺序:(由@ouah提供)

定义最小值(a,b)(a 扩展到:

(x++ < y ? x++ : y)
(x & 0xFF < 42 ? x & 0xFF : 42)
(x&0xFF<42?x&0xFF:42)

但是
&
的优先级低于
宏容易出错,因为它们依赖于文本替换,不执行类型检查。例如,此宏:

#define square(a) a * a
与整数一起使用时效果良好:

square(5) --> 5 * 5 --> 25
但是当与表达式一起使用时,会做一些非常奇怪的事情:

square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
在参数周围加括号有助于解决这些问题,但不能完全消除这些问题

当宏包含多个语句时,控制流构造可能会出现问题:

#define swap(x, y) t = x; x = y; y = t;

if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
最后,宏可能很难调试,产生奇怪的语法错误或运行时错误,您必须进行扩展才能理解(例如,使用gcc-e),因为调试器无法单步调试宏,如本例所示:

#define print(x, y)  printf(x y)  /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */

内联函数和常量有助于避免宏的许多此类问题,但并不总是适用的。在故意使用宏来指定多态行为的情况下,可能很难避免意外的多态。C++具有许多特征,如模板,可以帮助在不使用宏的情况下以类型化的方式创建复杂的多态结构;请参阅Stroustrup的C++编程语言。

< P>宏的一个缺点是调试器读取源代码,而没有扩展的宏,因此在宏中运行调试器并不一定有用。不用说,您不能像使用函数一样在宏中设置断点。

函数执行类型检查。这为您提供了一个额外的安全层。

如有疑问,请使用函数(或内联函数)

然而,这里的答案主要是解释宏的问题,而不是简单地认为宏是邪恶的,因为愚蠢的事故是可能的。
你可以意识到陷阱并学会避免它们。然后,只有在有充分理由的情况下才使用宏

在某些特殊情况下,使用宏有好处,包括:

  • 通用函数,如下所述,您可以有一个宏,可用于不同类型的输入参数
  • 可变数量的参数可以映射到不同的函数,而不是使用C的
    va_args

    例如:
  • 它们可以选择性地包含本地信息,例如调试字符串:
    \uuuuuuuuuu文件
    \uuuuuuu行
    \uuuu函数
    )。检查前置/后置条件、
    assert
    on failure,甚至静态断言,以便代码不会在不正确使用时编译(主要用于调试构建)
  • 检查输入参数,您可以在输入参数上执行测试,例如在强制转换前检查其类型、大小、结构成员是否存在(对于多态类型可能有用)。
    或检查数组是否满足某些长度条件。
    请参阅:
  • 虽然注意到函数执行类型检查,但C也会强制值(例如int/float)。在极少数情况下,这可能会有问题。可以编写比函数更精确的宏,这些宏与它们的输入参数有关。见:
  • 它们用作函数的包装,在某些情况下,您可能希望避免重复自己,例如<代码>函数(FOO,“FOO”)
,您可以定义一个宏来扩展字符串
func\u包装器(FOO)
  • 当您想在调用者的本地作用域中操作变量时,将指针传递给指针通常可以正常工作,但在某些情况下,使用宏仍然不太麻烦。
    (对于每像素操作,对多个变量的赋值是一个示例,您可能更喜欢宏而不是函数……尽管它仍然在很大程度上取决于上下文,因为
    inline
    函数可能是一个选项)
  • 诚然,其中一些依赖于非标准C的编译器扩展。这意味着您可能最终会得到更少的可移植代码,或者必须
    ifdef
    将它们放入,因此只有在编译器支持时才可以利用它们


    避免多参数实例化 注意这一点,因为这是宏中最常见的错误原因之一(例如,传入
    x++
    ,其中宏可能会多次递增)

    编写宏可以避免参数多次实例化的副作用

    如果你想让
    square
    宏与各种类型一起工作并支持C11,你可以这样做

    inline float           _square_fl(float a) { return a * a; }
    inline double          _square_dbl(float a) { return a * a; }
    inline int             _square_i(int a) { return a * a; }
    inline unsigned int    _square_ui(unsigned int a) { return a * a; }
    inline short           _square_s(short a) { return a * a; }
    inline unsigned short  _square_us(unsigned short a) { return a * a; }
    /* ... long, char ... etc */
    
    #define square(a)                        \
        _Generic((a),                        \
            float:          _square_fl(a),   \
            double:         _square_dbl(a),  \
            int:            _square_i(a),    \
            unsigned int:   _square_ui(a),   \
            short:          _square_s(a),    \
            unsigned short: _square_us(a))
    

    这是GCC、CLAN、EKOPATS和英特尔C++(但不是MSVC)支持的编译器扩展;


    因此,宏的缺点是,您需要知道如何开始使用这些宏,并且它们没有得到广泛的支持


    一个好处是,在这种情况下,您可以对许多不同的类型使用相同的
    square
    函数。

    添加到这个答案中

    宏被预处理器直接替换到程序中(因为它们基本上是预处理器指令),因此它们不可避免地会占用更多内存
    #define swap(x, y) t = x; x = y; y = t;
    
    if (x < y) swap(x, y); -->
    if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
    
    struct shirt 
    {
        int numButtons;
    };
    
    struct webpage 
    {
        int numButtons;
    };
    
    #define num_button_holes(shirt)  ((shirt).numButtons * 4)
    
    struct webpage page;
    page.numButtons = 2;
    num_button_holes(page) -> 8
    
    #define print(x, y)  printf(x y)  /* accidentally forgot comma */
    print("foo %s", "bar") /* prints "foo %sbar" */
    
    inline float           _square_fl(float a) { return a * a; }
    inline double          _square_dbl(float a) { return a * a; }
    inline int             _square_i(int a) { return a * a; }
    inline unsigned int    _square_ui(unsigned int a) { return a * a; }
    inline short           _square_s(short a) { return a * a; }
    inline unsigned short  _square_us(unsigned short a) { return a * a; }
    /* ... long, char ... etc */
    
    #define square(a)                        \
        _Generic((a),                        \
            float:          _square_fl(a),   \
            double:         _square_dbl(a),  \
            int:            _square_i(a),    \
            unsigned int:   _square_ui(a),   \
            short:          _square_s(a),    \
            unsigned short: _square_us(a))
    
    #define square(a_) __extension__ ({  \
        typeof(a_) a = (a_); \
        (a * a); })
    
    #define MIN(a,b) ((a)<(b) ? (a) : (b))
    
    int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));