编程嵌入式C工程师

C语言中的按位运算符(&, |, ^, ~, <<, >>)是如何工作的?它们在处理不同长度和符号类型时有哪些特殊性,开发人员在使用时常犯哪些错误?

用 Hintsage AI 助手通过面试

答案。

按位运算符控制整数类型的单独位:

  • & — 按位与
  • | — 按位或
  • ^ — 按位异或
  • ~ — 按位非
  • << — 左移
  • >> — 右移

特性:

  • 运算符仅适用于整数类型(intunsigned int等)。
  • 在右移(>>)时,带符号数(signed)可能会导致算术或逻辑移位——这取决于编译器。
  • 如果移位的比特数超过变量的位数,将会发生未定义的行为。
  • 为了确保可靠的操作,通常选择unsigned类型,以避免符号扩展。

示例:

unsigned int flags = 0; flags |= 0x1; // 设置第0位 flags &= ~0x2; // 清除第1位 if ((flags & 0x4) != 0) { /* ... */ } // 检查第2位

诱惑性问题。

带符号整数(signed int)和无符号整数(unsigned int)进行右移(>>)操作有什么区别?

常见的错误答案: 认为右移操作总是将零插入左边,不管符号如何。

正确答案: 对于unsigned int类型,右移(>>)总是插入零。对于signed int,要插入符号(如果数是负的则插入1)或零——这取决于编译器的实现(架构和C标准的规则)。

示例:

signed int a = -8; unsigned int b = (unsigned int)a; printf("%d\n", a >> 1); printf("%u\n", b >> 1);

在第一种情况下,结果依赖于编译器;在第二种情况下总是逻辑移位,并插入零。

由于对主题细节不了解而造成的真实错误示例。


故事

在协议处理代码中,信号标志存储在char类型中。程序员使用了8位移位(flag << 8),这由于溢出和类型提升规则导致数据丢失——结果总是为零。


故事

从网络协议中读取数据(大端模式)。使用按位操作来组合字节时,没有转换为unsigned,这有时导致在读取结构字段时出现意外的负值。


故事

使用~(按位非)来清除int类型值的位(例如,~0x80)被认为是0x7F,但实际上得到的结果是负数-129,这在后续计算和逻辑检查中导致错误。