C 在Linux内核中设置或清除位的独立于平台的方法

C 在Linux内核中设置或清除位的独立于平台的方法,c,linux-kernel,C,Linux Kernel,我目前正在阅读Robert Love的Linux内核开发第三版,在解释set\u bit(),clear\u bit()原子函数及其非原子同级函数,\uu set\u bit()和\uu clear\u bit()的一节后,我遇到了一个奇怪的语句: 与原子整数运算不同,代码通常无法选择是否使用逐位运算它们是设置特定位的唯一可移植方式。 -p。183(强调我自己) 我知道这些操作可以在单个特定于平台的汇编指令中实现,这就是为什么存在这些内联函数的原因。但我很好奇,为什么作者说这些是做这些事情的唯一

我目前正在阅读Robert Love的Linux内核开发第三版,在解释
set\u bit()
clear\u bit()
原子函数及其非原子同级函数,
\uu set\u bit()
\uu clear\u bit()
的一节后,我遇到了一个奇怪的语句:

与原子整数运算不同,代码通常无法选择是否使用逐位运算它们是设置特定位的唯一可移植方式。

-p。183(强调我自己)

我知道这些操作可以在单个特定于平台的汇编指令中实现,这就是为什么存在这些内联函数的原因。但我很好奇,为什么作者说这些是做这些事情的唯一便携式方法。例如,我相信我可以在
无符号长x
中非原子地设置位
nr
,方法是在普通C中执行此操作:

x |= 1UL << nr;
当然,根据编译器的复杂程度,这些指令可能会编译成多条指令,因此它们可能不如
\uu set\u bit()
\uu clear\u bit()
函数好

我是不是遗漏了什么?这句话只是一个稍微懒散的简化,还是我上面介绍的设置和清除位的方法有什么不重要的地方

编辑:尽管GCC非常复杂,但它仍然执行位移位,而不是像
\uu set\u bit()
函数那样使用单个指令,即使在
-O3
(版本6.2.1)上也是如此。例如:

stephen at greed in ~/code 
$ gcc -g -c set.c -O3         

stephen at greed in ~/code 
$ objdump -d -M intel -S set.o

set.o:     file format elf64-x86-64


Disassembly of section .text.startup:

0000000000000000 <main>:
#include<stdio.h>
int main(int argc, char *argv)
{
  unsigned long x = 0;
  x |= (1UL << argc);
   0:   89 f9                   mov    ecx,edi
   2:   be 01 00 00 00          mov    esi,0x1
   7:   48 83 ec 08             sub    rsp,0x8
   b:   48 d3 e6                shl    rsi,cl
   e:   bf 00 00 00 00          mov    edi,0x0
  13:   31 c0                   xor    eax,eax
  15:   e8 00 00 00 00          call   1a <main+0x1a>
{
  1a:   31 c0                   xor    eax,eax
  x |= (1UL << argc);
  1c:   48 83 c4 08             add    rsp,0x8
  printf("x=%x\n", x);
  20:   c3                      ret 
stephen在贪婪中~/code
$gcc-g-c套装.c-O3
斯蒂芬在~/code中贪婪
$objdump-d-M intel-S set.o
set.o:文件格式elf64-x86-64
分解.text.startup节:
0000000000000000 :
#包括
int main(int argc,char*argv)
{
无符号长x=0;
x |=(1UL在原子整数运算的上下文中,“按位运算”也指原子整数运算


非原子逐位操作没有什么特别之处(除了它们支持大于每_长位的数字),因此始终正确,并且只需要特定于体系结构的实现来优化性能。

如果您切换到(
已签名的
)的符号位,这就是UB
long
.Fair point--内核操作是在
void*
上定义的,但为了简单起见,我决定使用整数。我将它们更改为
无符号long
。您的编辑不会改变任何内容。
1L现在,如果您想在
有符号long
中设置特定位,您会遇到麻烦,因为如果要设置的位是e符号位,您发布的代码会导致实现定义的行为。如果您查看这些函数的作用,您会发现
nr
并不局限于一个单词。因此,单词大小、对齐要求的可移植性问题都适用于此。此外,如果您查看实际实现,您将看到相当多的平台尝试通过将“读-修改-写”的数量减少到较短的数据类型(如
char
int
)来“优化”它。IMO是一种通用实现,它在
long*
上“读-修改-写”非常可移植,但在LP64平台(Linux支持的所有/大多数64位平台)上存在潜在的性能劣势非常好,我从来都不知道那些通用实现的存在。
stephen at greed in ~/code 
$ gcc -g -c set.c -O3         

stephen at greed in ~/code 
$ objdump -d -M intel -S set.o

set.o:     file format elf64-x86-64


Disassembly of section .text.startup:

0000000000000000 <main>:
#include<stdio.h>
int main(int argc, char *argv)
{
  unsigned long x = 0;
  x |= (1UL << argc);
   0:   89 f9                   mov    ecx,edi
   2:   be 01 00 00 00          mov    esi,0x1
   7:   48 83 ec 08             sub    rsp,0x8
   b:   48 d3 e6                shl    rsi,cl
   e:   bf 00 00 00 00          mov    edi,0x0
  13:   31 c0                   xor    eax,eax
  15:   e8 00 00 00 00          call   1a <main+0x1a>
{
  1a:   31 c0                   xor    eax,eax
  x |= (1UL << argc);
  1c:   48 83 c4 08             add    rsp,0x8
  printf("x=%x\n", x);
  20:   c3                      ret