C++中按位运算符的结果

10

测试几个编译器(Comeau, g++)可以确认,“整数类型”的按位运算结果是一个int型:

void foo( unsigned char );
void foo( unsigned short );

unsigned char a, b;

foo (a | b);

我本来认为 "a | b" 的类型应该是无符号字符,因为两个操作数都是无符号字符,但编译器却说结果是整型,并且对 foo() 的调用不明确。为什么语言的设计是让结果成为整型,还是这取决于实现?

谢谢。

6个回答

11

实际上这是C++的标准行为(ISO/IEC 14882):

5.13/1 按位包含 OR 运算符

执行通常的算术转换;结果是其操作数的按位包含 OR 函数。此运算符仅适用于整数或枚举操作数。

5/9 通常的算术转换

许多二元运算符希望算术或枚举类型的操作数进行转换,并以类似的方式产生结果类型。目的是产生一种通用类型,该类型也是结果的类型。这种模式称为 通常的算术转换,定义如下:

  • 如果任何一个操作数的类型是 long double,则另一个操作数将被转换为 long double
  • 否则,如果任何一个操作数是 double,则另一个操作数将被转换为 double
  • 否则,如果任何一个操作数是 float,则另一个操作数将被转换为 float
  • 否则,两个操作数都将执行整数提升。
  • ......

4.5/1 整数提升

如果源类型的所有值都可以由 int 表示,那么 char、signed char、unsigned char、short int 或 unsigned short int 类型的 rvalue 可以转换为 int 类型的 rvalue;否则,源 rvalue 可以转换为 unsigned int 类型的 rvalue。

我认为这与int被认为是执行环境中“自然”的大小以实现高效算术有关(请参见Charles Bailey的答案)。


1
看起来它说“可以”。换句话说,如果您的编译器可能会或可能不会提升为int。 - T.E.D.
@T.E.D.:它说“可以是”,因为目标类型可以是intunsigned int。这是此处唯一存在的“可以是”。至于提升本身,它总是无条件地要求的。 - AnT stands with Russia
@AndreyT - 这不是真的。 "...如果int可以表示源类型的所有值,则可以将其转换为...;否则...可以转换为... unsigned int"。由于第二个选项中使用了“可以”,整个转换是可选的。 - Merlyn Morgan-Graham
@T.E.D,Merlyn:不,AndreyT是正确的。这里的“can be”定义了可能存在的整数提升。通常算术转换规则规定必须应用整数提升。编译器有自由决定可以进行哪种提升(通过选择类型的宽度),但在做出选择后,它“必须”执行提升。无论如何,“can be”在C++标准中通常意味着它是可能发生的事情,程序员可以引起它发生,而不是可选功能。 - Steve Jessop
2
@Merlyn Morgan-Graham:不,此文中的所有“可以”都来自于该规范部分对“更小”的类型范围等实现细节的依赖。但类型提升本身是不可避免的。C/C++从不对比int/ unsigned int更小的类型执行算术运算。 - AnT stands with Russia
1
@T.E.D,Steve Jessop,AndreyT:糟糕,错过了“否则,将执行整数提升”的部分。用杰夫·福克斯沃西的话说,“如果您的语言存在复杂性问题,那么...” - Merlyn Morgan-Graham

1
我本来以为"a | b"的类型应该是unsigned char,因为两个操作数都是unsigned char。
我曾经阅读过一些初学者C语言书籍,留下了这样的印象:按位运算符仅在语言中用于系统编程目的,通常应避免使用。
这些运算符由CPU本身执行。 CPU使用寄存器作为操作数(肯定比char大),因此编译器无法知道操作将影响寄存器的多少位。为了不丢失操作的完整结果,编译器将结果向上转换为适当的操作。据我所知。
为什么语言设计成结果是int,还是这个实现有关?
数据类型的位级表示实际上是实现定义的。这可能是为什么明显按位操作也是实现定义的原因。
尽管C99在“6.2.6.2整数类型”中定义了它们应该如何出现和行为(以及后来按位操作应该如何工作),但特定章节给予了实现很大的自由度。

0

看起来这与Java中的情况相同:

Short和char(以及其他比int小的整数)是比int更弱的类型。因此,对这些较弱类型的每个操作都会自动解包为int。

如果您真的想获得一个short,那么您将不得不进行类型转换。

不幸的是,我无法告诉您为什么要这样做,但似乎这是一个相对常见的语言决策...


short 转换为 int 并不是 '拆箱'。拆箱是指像 Integerint 这样的操作(相反的过程是 '装箱')。short 转换为 int 只是一个隐式转换(与显式转换相对应:int a = 123; short b = (short)a;)。 - Brian S

0

short 不就是和 short int 一样吗?就像 longint 是同义词一样。例如,一个 short 是一个比标准的 int 占用更少内存的 int 吗?


1
long 不等同于 int。这些类型之间的关系是 char <= short <= int <= long,并且还有额外的规则规定每种类型的最小大小。 - James McNellis

0

int 应该是任何给定机器架构的自然字长,许多机器都有仅(或至少最优地)在机器字上执行算术运算的指令。

如果语言没有定义整数提升,许多多步计算本来可以直接映射到机器指令中,但现在必须插入掩码操作以生成“正确”的结果。


就我个人而言,我仍然对此有疑问。中间掩码是改变最终结果还是不改变的。如果不改变,则编译器可以跳过它。如果改变,则“不能比int更小”的规则引入了“出乎意料的行为”——快速结果与操作数类型不符的“错误”结果。另一种选择就是根本不定义任何在其合理“预期”含义下“太慢”的运算符。当然,那些年的C语言黑客并没有我们现在对可用性的相同期望;-) - Steve Jessop
更有说服力的论点是,如果你在short类型上进行算术运算,迟早需要检查溢出,因此编译器最好能够帮助你避免错误。 - Steve Jessop
1
@David:如果仅有的操作码适用于整数,则使用整数进行计算,然后将其转换为所需形式。但这不是“至少提升为int”的准确结果。如果a和b是16位宽度的unsigned short,则问题在于(a + b) & 0x10000是否可能非零。采用“至少提升为int”规则,如果我们只使用“正确”的短算术运算,则可以。因此,实际上,“提升为int”的规则在这个示例中已经创建了一个减速作用;-) - Steve Jessop
@Steve314:intlong有时会溢出,但我的实际经验是,如果你进行了除增量之外的大量算术运算,short几乎总是会溢出,因为我很少遇到数字保持那么小的实际问题。那些在16位微处理器上进行过大量编程的人可能会持不同意见:他们可能正在使用更小的数字来解决更小的问题;至于截断“令人惊讶”,我指的是从扫描代码的人的角度来看是令人惊讶的,而不是从没有概念计算机可能具有有限能力的人的角度来看是令人惊讶的。 - Steve Jessop
@Steve - 这很有趣,因为我认为你说的是“至于截断是‘令人惊讶’- 我是指从扫描代码的人的角度来看,而不是从没有概念计算机可能有限制能力的人的角度来看。 ”虽然我认为你误解了我。我不认为整数算术行为是错误的。我认为你声称“编译器可以帮助你避免错误”是错误的 - C和C ++都没有习惯于猜测程序员是否真的打算做他所做的事情。 - user180247
显示剩余6条评论

0

无论是 C 还是 C++,它们都不会对比 int 更小的类型进行任何算术运算。每当您指定一个更小的操作数(任何类型的 charshort),该操作数都会被提升为 intunsigned int,具体取决于范围。


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