是否有充分的理由在 C 中始终将定义括在括号中?

Posted

技术标签:

【中文标题】是否有充分的理由在 C 中始终将定义括在括号中?【英文标题】:Is there a good reason for always enclosing a define in parentheses in C? 【发布时间】:2012-02-23 06:52:31 【问题描述】:

显然,#define 语句有时必须有括号,如下所示:

#define WIDTH 80+20

int a = WIDTH * 2; // expect a==200 but a==120

所以我总是用括号括起来,即使它只是一个数字:

#define WIDTH (100)

C 新手问我为什么要这样做,所以我试图找到一个极端情况,即单个数字 #define 上没有括号会导致问题,但我想不出一个。

这种情况存在吗?

【问题讨论】:

我将把它留在这里:parashift.com/c++-faq-lite/inline-functions.html#faq-9.5(查找为什么#define 宏是邪恶的链接)。 @TReddy 谢谢,但你不会用inline int width() return 100; 替换#define WIDTH (100) 对吧?! 不,我想分享为什么宏是邪恶的链接,而这些链接恰好是在关于内联函数的那部分收集的(这与你的问题正交)。在您的情况下,宏是否比static int width = 80+20 提供更好的结果? @TReddy 这是一个好点,快速测试显示唯一真正的缺点是我收到一堆警告,它是在 .h 文件中定义的,但不是在每个 .c 文件中使用包括它。也许可以关闭该警告,然后这将是一个可行的替代方案。 @Philip 谢天谢地!那里的错字会导致什么! #define 100 50-50 【参考方案1】:

由于100 是单个标记,我怀疑您会找到括号很重要的极端情况(对于单个标记!)

这仍然是 IMO 的一个好习惯,因为当涉及多个令牌时它们可能很重要。

【讨论】:

【参考方案2】:

只要定义包含单个标记(只有一个操作数,没有运算符),就不需要括号,因为单个标记(例如 100)在词法分析和解析时是不可分割的原子。

【讨论】:

【参考方案3】:

没有。 #define WIDTH 100 不会产生明确的或“令人惊讶的”扩展。那是因为它只能导致单个令牌被单个令牌替换。

如您所知,当单个标记(例如WIDTH)导致多个标记(例如80 + 20)时,会出现宏混淆。据我推测,这是在替换中使用括号的唯一原因,正如我在第一段中所探讨的,它不适用于此处。

但是,抛开这个技术事实不谈,它可能仍然是一个很好的做法。它可以促进习惯,如果该宏被修改为更复杂的东西,它也可以作为提醒。

【讨论】:

【参考方案4】:

有时是有充分理由的。

对于一个单一的数字,没有充分的理由。

对于其他情况,正如您所展示的那样,这是有充分理由的。

有些人喜欢格外小心,总是使用括号(@aix 推荐它。我不这样做,但没有硬性答案)。

【讨论】:

感谢@Lightness 的编辑。我意识到这些年来我一直拼错括号...... 仅供参考,单数是括号。 :) 是的,我刚刚查过了。此外,整个内容(括号加上其中的内容)是一个括号(在英语中,我不确定 C)。 确实,这和语言学有关;我认为这与技术语言无关。 顺便说一句,@LightnessRacesinOrbit,这里有 3 个人(包括我),使用“括号”,4 使用“括号”(包括你)。所以至少我并不孤单。【参考方案5】:

这当然不会伤害,这是一个好习惯。但是(100)100对于数值计算没有区别。

【讨论】:

【参考方案6】:

有时您在编写代码时必须考虑到当前的注意事项,而不是考虑下次将要编辑的内容

现在你的宏是一个整数。想象一下将来有人编辑它。假设他们不是你,而是一个不那么小心或比较匆忙的人。括号用于提醒人们在其中进行任何修改。

这种想法在 C 语言中是一个好习惯。我个人编写代码的风格可能有些人可能会觉得“冗余”,类似这样的东西,尤其是在错误处理方面。冗余是为了将来编辑的可维护性和可组合性。

【讨论】:

如果有人知道非平凡的宏需要括号,他会在更改时添加它们。否则,无论您做什么,他都会造成混乱。所以我反对添加显然不需要的括号。但这是一个见仁见智的问题。 我不同意——您不必担心非程序员将来维护您的代码的可能性。宏表达式周围的括号是如此基本的东西,您应该能够假设每个 C 程序员都知道它。否则,通过使用相同的参数,您应该在 everything 周围加上括号:int x = y + z;(不是宏)应该使用相同的有缺陷的逻辑始终写为int x = (y + z);,以防强调的非程序员将来会维护代码,提醒他们运算符优先级的危险。 好的,伙计们,那我不同意你的不同意见。 :-) 特别是@Lundin 的 cmets,我说的不是非程序员,而是坏或粗心的程序员——它们似乎成群结队地存在,在某些工作环境中,你无法控制谁来维护你的代码线。但即使你有一群体面的程序员,也许括号不是最好的例子,但我坚信添加更多的行和字符通常可以使编辑更“可组合”——我们不应该总是努力编写尽可能短的代码仅仅因为我们可以。 这与在if(foo) bar() 周围使用大括号的论点相同。当然,你可以把牙套放在外面。很明显,当您添加 baz() 时,您将需要它们。直到凌晨 3 点,这一切都很明显...... 我同意@ChrisBurt 的观点,这与在所有块周围都有大括号一样,但我不同意他的结论:我从来没有买过这种“哦,你可以忘记它”的论点。以我的经验,这根本就不是问题,使潜在的问题完全是理论上的。【参考方案7】:

是的。预处理器串联运算符 (##) 会导致问题,例如:

#define _add_penguin(a) penguin ## a
#define add_penguin(a) _add_penguin(a)

#define WIDTH (100)
#define HEIGHT 200    

add_penguin(HEIGHT) // expands to penguin200
add_penguin(WIDTH)  // error, cannot concatenate penguin and (100) 

字符串化也是如此 (#)。显然,这是一个极端情况,考虑到WIDTH 的使用方式可能并不重要。尽管如此,关于预处理器还是需要牢记这一点。

(添加第二个企鹅失败的原因是 C99 中预处理规则的一个微妙细节 - iirc 它失败是因为连接到两个非占位符预处理标记必须始终产生单个预处理标记- 但这无关紧要,即使允许串联,它仍然会给出与未加括号的 #define!) 不同的结果。

所有其他响应都是正确的,仅在从 C++ 扫描器的角度来看无关紧要的情况下,因为事实上,数字是原子的。但是,根据我对问题的阅读,没有迹象表明只应考虑没有进一步预处理器扩展的情况,因此即使我完全同意其中包含的建议,其他回答也是错误的。

【讨论】:

实际上,对我来说,只要我定义了一个 penguin200 和一个 penguin(int a) 函数,它就可以很好地编译。将 penguin 和 (100) 连接起来形成 penguin(100) 似乎没有问题,这是成功的调用。不过,我可能正在使用 C89。 是的,这可能是由于不同的预处理器规范(gnu cpp 在我的机器上失败)。但我认为我们同意这与问题无关...... 从技术上讲,我询问了没有括号会产生问题的情况。这是由括号的存在引起的问题。然而,这比“没有区别”更好。 @Lundin 对你有好处。但是很多人使用它并且需要它。 Boost for instance 很好地利用了无法替换的令牌粘贴。我也已经在图书馆需要它,但我知道我的情况没有解决方法。 我不会像@Lundin 建议的那样禁止##,但如果你使用它,你应该知道你在做什么。因此,因此要求在宏周围加上括号似乎是错误的。这是一个有趣的预处理器角落,但与“正常”预处理器使用无关。【参考方案8】:

正如Blagovest Buyukliev所说:

定义由单个标记组成(只有一个操作数,没有运算符),不需要括号,因为单个标记(例如 100)在词法分析和解析时是不可分割的原子。

但在宏方面,我会推荐以下规则:

    避免使用宏之类的函数@see Lundin 的评论。

如果您想使用宏之类的函数,请考虑以下两条规则:

    在宏中始终使用括号作为参数 宏参数只使用一次

为什么要规则 1.? (保持操作顺序正确)

#define quad(x) (x*x)
int a = quad(2+3);

将扩展为:

int a = (2+3*2+3);

为什么要规则 2.? (确保副作用只应用一次)

#define quad(x) (x*x)
int i = 1;
int a = quad(i++);

将扩展为:

int a = i++ * i++;

【讨论】:

稍微考虑一下,类似函数的宏可以引用非参数上下文变量,而内联不能。但就像你说的,没有真正好的理由使用这样的东西。我是前雇主的一个非常大的代码审查的一部分,我们最终编写了很多内联代码,违反公司编码政策,以替换散布在我们操作系统部分的大量复制代码,否则这些代码将被编写为函数- 类似宏。我们最终从 #1 的错误来源变成了如何正确处理的模型。 @Lundin 一个案例是通用“函数”,例如max。在 C++ 中,您可以将它们实现为多参数模板。 C 中唯一真正灵活的选项是类似函数的宏。【参考方案9】:

当代码只定义一个数字时,@Alexander Gessler 很好地回答了这个问题。

然而,许多编码人员并没有注意到以下一元运算符

#define TEMPERATURE1M (-1)
#define TEMPERATURE1P (+1)

当代码使用采用运算符的#define 时,将() 括起来可确保预期的数字结果和优先级。

#define TEMPERATURE_WITH  (-1)
#define TEMPERATURE_WITHOUT -1

// Consider how these will compile
int w  = 10-TEMPERATURE_WITH;
int wo = 10-TEMPERATURE_WITHOUT;  // May not compile

最后一行代码可以编译给定 C99 语义更改@Olaf

【讨论】:

以上是关于是否有充分的理由在 C 中始终将定义括在括号中?的主要内容,如果未能解决你的问题,请参考以下文章

DevExpress XtraGrid 格式 - 如何将所有负数括在括号中

是否有正当理由在C程序的main()函数中声明变量static?

是否有充分的理由在单平台系统上使用 .proto 文件?

是否有充分的理由避免在 JS 中没有最终表达式的 FOR 循环?

是否有充分的理由在 Program.cs/main 中编写代码而不是使用类? [关闭]

是否有充分的理由在打字稿项目中将编译器选项“声明”设置为 true