C、C++ 预处理器宏

4

有人能解释一下这是如何工作的吗?

#define maxMacro(a,b) ( (a) > (b) ) ? (a) : (b) 

inline int maxInline(int a, int b)
{
  return a > b ? a : b;
}

int main()
{  
  int i = 1; j = 2, k = 0;
  k = maxMacro(i,j++); // now i = 1, j = 4 and k = 3, Why ?? Where is it incremented ?
  //reset values
  i = 1; j = 2, k = 0;
  k = maxInline(i,j++); // now i = 1, j = 3, and k = 2, Why ??
  return 0;
}

所以,我想知道在检查条件、返回或调用时,j的值到底是在哪里增加的?

  • a. 使用宏
  • b. 使用内联方法

更新: 谢谢大家,现在我明白了。但只是出于好奇,为什么有人会在调用方法时进行j++,而不是在调用方法后增加j,这样会更少引起混淆。我在某个地方看到了这段代码,所以问一下!


1
一个好的编程习惯确实是,如果不必要的话,在同一条指令中不要进行递增。另一个好习惯是永远不要调用max宏,永远禁止使用它。 - moala
关于那个问题,因为它可以节省打字。 - rlbond
我相信易读、不混淆的代码比少打但混淆的代码更重要。这将为未来节省时间。 - seg.server.fault
7个回答

21
问题在于预处理器只对宏进行直接的文本替换。
maxMacro(i, j++)

成为

( (i) > (j++) ) ? (i) : (j++)

从这里可以看出,当j大于时,它会进行两次递增操作。

这正是为什么你应该优先选择内联函数而不是宏的原因。


同意,参见https://www.securecoding.cert.org/confluence/display/seccode/PRE31-C.+Never+invoke+an+unsafe+macro+with+arguments+containing+assignment%2C+increment%2C+decrement%2C+volatile+access%2C+or+function+call。 - Andrew Keeton

10
k = maxMacro(i,j++);

扩展为:

k = ( (i) > (j++) ) ? (i) : (j++)

由于?:的序列点,此操作的行为是定义良好的。当i小于j的初始值时,j会被增加两次,而k接收的是j一次递增后的值。

(如果i大于j的初始值,则在评估(i) > (j++)时,j只会递增一次,k将被赋予(i)的值,并且第二个递增不会执行.)

代码:

k = maxInline(i,j++);

在调用maxInline函数之前,使用值ij来调用函数,此时j的值为增加1(即为2),函数调用之前将j自增。而变量k被赋值为maxInline函数的返回值2。


6

宏在文本扩展中起作用。它发生在编译器考虑表达式和运算符之前,也是在将输入文本分成单个标记后立即发生的。因此,以下行:

k = maxMacro(i,j++);

在宏展开后,完全等同于以下行:

k = ( (i) > (j++) ) ? (i) : (j++);

显然,这里有两个增量。

另一方面,内联函数只是函数,并且在调用目的上与非内联函数完全相同。在函数调用中,参数列表中的表达式首先被评估,然后它们的值被绑定到函数体内的相应参数名称。因此,该评估只发生一次。


5
这就是为什么宏是邪恶的原因!
宏是在编译器处理之前由预处理器进行文字替换,因此k = maxMacro(i,j++);变成了( (i) > (j++) ) ? (i) : (j++);。我希望你能看到这里的问题。
在(内联)函数调用中,a和b的值被按值传递到函数中,在函数中,i和j的初始值被传递进去,之后j会自增。

行为是良好定义的,在 ?: 中有一个序列点。 - CB Bailey
有吗?哎呀,那我就把那一部分删掉了。 - KTC

3
调用宏并不同于调用函数。预处理器会将对maxMacro(i, j++)的引用替换为文本,其格式如下:
(i)>(j++)?(i):(j++)
后置增量运算符使用其目标的当前值,然后对其目标进行递增操作。因此,如果i = 1,j = 2,则会发生以下情况:
(i)>(j++) // 1与2进行比较,并将j递增至3 ?(i):(j++) 2大于1,因此传递“b”值。 j++再次被调用,所以前一个值3作为表达式的结果返回, 但是作为副作用,j会递增至4。
另一方面,内联函数与非内联函数在参数变量处理方式上完全相同。 当j++放入函数调用堆栈中时,它只被引用一次。 函数调用在具有b值为2的情况下运行并返回其结果(即2), 同时副作用是将j递增至3。
注意:您的问题表明在调用maxInline后k = 3。我得到了那个调用之后k = 2的结果-这是我预期的结果。
希望这可以澄清事情...
K

0

0

奇怪。到目前为止,还没有人提到内联函数选项是类型安全的,而宏不是!


1
该宏是类型安全的,只有在两种类型上定义了operator>运算符并且存在到一个公共结果类型的隐式转换时才能正常工作。 - bdonlan
但它不强制要求两个参数使用相同的类型。如果你尝试用char*和int类型来调用它,你就会明白我的意思。 - OJ.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接