为什么使用三元运算符在宏中定义1和0?


79

我正在将SDK用于嵌入式项目。在此源代码中,我找到了一些至少是我特有的代码。SDK的许多地方都有这种格式的源代码:

#define ATCI_IS_LOWER( alpha_char )  ( ( (alpha_char >= ATCI_char_a) && (alpha_char <= ATCI_char_z) ) ? 1 : 0 )

#define ATCI_IS_UPPER( alpha_char )  ( ( (alpha_char >= ATCI_CHAR_A) && (alpha_char <= ATCI_CHAR_Z) ) ? 1 : 0 )

在这里使用三元运算符有什么区别吗?

是不是

#define FOO (1 > 0)

与...相同

#define BAR ( (1 > 0) ? 1 : 0)

我尝试通过使用进行评估

printf("%d", FOO == BAR);

并得到结果1,所以看起来它们相等。是否有理由像他们一样编写代码?


8
不,没有理由。你是对的。
艺术

29
部分偏离主题:何时停止使用预处理器的疯狂?这里涉及功能的潜在多重评估。只是没有必要。
stefan

3
有时也很明确。这里的三元运算符使我们一目了然,宏的目的是返回布尔值。

5
至少,宏应该使用(alpha_char)而不是alpha_char,以确保如果有人尝试执行类似的疯狂操作ATCI_IS_LOWER(true || -1),宏也不会中断。
贾斯汀时间-恢复莫妮卡

5
看起来像是很久以前写的CI。我是从Pascal来到C的,Pascal有专门的boolean类型,所以我浪费了无数的时间来改变类似if (n)to的恐怖if (0 != n),可能添加了一个可疑的“确保”。我敢肯定,我也能证明像这样的不平等现象if (a < b) ...。当然看起来像Pascal的if a < b then ...,但是我知道C <不是aboolean而是an int,an几乎int可以是任何东西!恐惧导致镀金,镀金导致偏执狂,偏执狂导致...这样的代码。
凯文·蔡斯

Answers:


131

您是正确的,在C中它是不言自明的。您的特定三元条件 (1 > 0)都属于类型int

但它关系在C ++中,虽然,在一些好奇的角落情况下(例如作为参数传递给函数重载),因为三元条件表达式类型int,而(1 > 0)为型bool

我的猜测是作者为了保留C ++兼容性而对此进行了一些思考。


2
我认为bool <-> int§4.7/ 4中的转换是C ++中标准的隐式转换(整数转换),那么这有什么关系呢?
Motun

70
考虑一个函数的两个重载foo,一个采取const bool&其他回吐const int&。其中一个付钱给您,另一个给您重新格式化硬盘。在这种情况下,您可能需要确保调用正确的重载。
芭丝谢芭

3
通过将结果强制转换为结果int而不是使用三元数来处理这种情况不是更明显吗?
martinkunev

18
@Bathsheba虽然是一个合理的极端情况,但是使用积分重载来实现这种不一致行为的任何程序员都是完全邪恶的。
JAB

7
@JAB:您不必变得邪恶,您只需要犯(常见)错误,即编写一段代码,该代码会根据整数类型意外地执行两种不同的操作(或更糟糕的是,调用未定义的行为),并具有在一个可能触发根本不同的代码路径的地方这样做的不幸。

28

有些整理工具认为比较的结果是布尔值,不能直接在算术中使用。

不要说出名字或用任何手指指责,而是PC-lint就是这种掉毛工具

我并不是说它们是正确的,但这是对为什么这样编写代码的可能解释。


10
Not to name names or point any fingers,但是你都做到了,大声笑。
StackOverflow17年

19

有时,您会在非常古老的代码中看到这一点,从此之前就已经有C标准可以说明(x > y)数字1或0了;一些CPU宁愿将其求值结果改为-1或0,而一些非常老的编译器可能只是紧随其后,因此一些程序员认为他们需要额外的防御能力。

有时你会也看到这一点,因为类似的表述并不一定计算为数字1或0。例如,在

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)

内部&-expression的计算结果为0或的数值F_DO_GRENFELZ(可能不是1),因此? 1 : 0可以规范化它。我个人认为将其写为

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)

但是理性的人会不同意。如果您连续使用一整堆这些代码,并测试不同类型的表达式,那么可能有人会认为,将所有这些表达式放到? 1 : 0最后要比担心哪个实际需要它更容易维护。


总的来说,我更喜欢使用!!( expr )布尔值的规范化,但是如果您不熟悉布尔值,我会承认它会造成混淆。
PJTraill

1
@PJTraill每次在括号内放置空格时,上帝就会杀死一只小猫。请。想想小猫。
zwol

这是我听说不在C程序中放在方括号内的最佳原因。
PJTraill

15

SDK代码中有一个错误,三元组可能是解决它的大手笔。

作为宏,参数(alpha_char)可以是任何表达式,并应加上括号,因为诸如'A'&&'c'的表达式将使测试失败。

#define IS_LOWER( x ) ( ( (x >= 'a') && (x <= 'z') ) ?  1 : 0 )
std::cout << IS_LOWER('A' && 'c');
**1**
std::cout << IS_LOWER('c' && 'A');
**0**

这就是为什么应该始终在扩展中用括号括住宏参数的原因。

因此,在您的示例(但带有参数)中,这两个都存在问题。

#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)

它们将最正确地被替换为

#define BIM(x) ((x) > 0)

@CiaPan在下面的注释中很重要,那就是多次使用参数会导致无法定义的结果。例如

#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1** 
**BUT ch is now '{'**

4
另一个错误是,该参数使用了两次,所以具有副作用的说法会导致不可预测的结果:IS_LOWER(++ var)可以增加var一次或两次,另外它可能没有注意到,承认小写'z',如果var'y'宏调用之前。这就是为什么应该避免使用此类宏,或者仅将参数转发给函数的原因。
CiaPan

5

在C语言中没关系。C中的布尔表达式的类型int和值均为01,因此

ConditionalExpr ? 1 : 0

没有效果。

在C ++中,它实际上是强制转换为int,因为C ++中的条件表达式具有type bool

#include <stdio.h>
#include <stdbool.h>

#ifndef __cplusplus

#define print_type(X) _Generic(X, int: puts("int"), bool: puts("bool") );

#else
template<class T>
int print_type(T const& x);
template<> int print_type<>(int const& x) { return puts("int"); }
template<> int print_type<>(bool const& x) { return puts("bool"); }


#endif

int main()
{
    print_type(1);
    print_type(1 > 0);
    print_type(1 > 0 ? 1 : 0);

/*c++ output:
  int 
  int 
  int

  cc output:
  int
  bool
  int
*/

}

也可能没有预期的效果,并且作者只是认为它使代码更清晰。


顺便说一句,我认为C_Bool现在应该具有,_Bool和,因此C应该遵循suite并进行布尔表达式_Generic。鉴于所有较小的类型int在大多数情况下都可以自动升级为大多数代码,因此它不会破坏太多的代码。
PSkocik's

5

一个简单的解释是,有些人或者不理解条件会在C中返回相同的值,或者他们认为编写条件更干净((a>b)?1:0)

这就解释了为什么有些人在具有适当布尔值的语言中也使用类似的构造,在C语法中为(a>b)?true:false)

这也解释了为什么您不必不必要地更改此宏。


0

作为嵌入式软件,也许会提供一些线索。也许有许多宏都是使用这种样式编写的,以方便暗示ACTI行使用直接逻辑而不是反向逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.