什么是CHAR_BIT?

117

引用自http://graphics.stanford.edu/~seander/bithacks.html,用于在没有分支的情况下计算整数绝对值(abs)的代码:

int v;           // we want to find the absolute value of v
unsigned int r;  // the result goes here 
int const mask = v >> sizeof(int) * CHAR_BIT - 1;

r = (v + mask) ^ mask;

专利变种:

r = (v ^ mask) - mask;

什么是CHAR_BIT,如何使用它?


注意:v + mask 可能会导致 int 溢出 - 这是_未定义的行为_。(v ^ mask) - mask 也可能存在类似的问题。 - chux - Reinstate Monica
这段代码的编写方式不符合标准,而且存在危险。一般来说,对有符号数进行位移操作很容易引发实现定义行为(对负数进行右移)甚至未定义行为(将1位左移到符号位),因此应该避免使用。此外,加法操作也可能导致未定义行为。根据标准,使用无符号整数来重写这段代码应该很容易,因为它们在所有平台上都有明确定义的行为。 - undefined
它还假设负值采用二进制补码表示。我意识到这段代码为了避免条件语句而变得非常荒谬,但这可能是最简单、最易理解和最具可移植性的做法。 - undefined
3个回答

262

CHAR_BITchar类型中的位数。如今,几乎所有架构都使用每个字节8个比特位,但这并不总是情况。一些旧机器曾经使用7位字节

它可以在<limits.h>中找到。


6
有些数字信号处理器拥有10个或更多比特字节。 - Juri Robl
74
C语言要求 CHAR_BIT>=8 ,并允许使用单一类型大小的DSPs(数字信号处理器)使用更大的值,通常为32位。而POSIX则要求 CHAR_BIT==8。一般来说,你可以假设任何多用户/多任务服务器导向或交互使用的架构,如果有任何与互联网相连的可能性或需要与外部交换文本数据,则 CHAR_BIT==8 - R.. GitHub STOP HELPING ICE
7
@caf:不,C99要求存在类型int8_tuint8_t,因此存在宽度为8的类型。由于任何类型的sizeof必须与sizeof char兼容,实际上sizeof int8_t必须为1。所以CHAR_BIT == 8。我已经围绕这个观察写了一些东西,在这里:https://gustedt.wordpress.com/2010/06/01/how-many-bits-has-a-byte/ - Jens Gustedt
28
请引用C99规范中的一节。关于精确宽度整数类型,C99规范表示:“这些类型是可选的。”(7.18.1.1/3)。但最小宽度和最快宽度类型是必需的。 - jamesdlin
4
@jamesdlin和caf:抱歉我搞混了事情。是的,我提到的要求实际上来自于stdint.h的POSIX规范。因此,在那里,它是必需的,并且还标记为“扩展ISO C标准”,而不引用该标准的特定版本。我的错误。 - Jens Gustedt
显示剩余8条评论

5
尝试回答原问题中显式的问题(什么是CHAR_BIT)和隐含的问题(这是如何工作的)。
在C和C++中,char代表C程序可以寻址的最小内存单元*。
在C和C++中,CHAR_BIT代表一个char中的位数。由于对char类型的其他要求,它必须至少为8。在所有现代通用计算机上,它实际上恰好为8,但一些历史悠久或专业系统可能具有更高的值。
Java没有CHAR_BITsizeof的等效物,因为Java中的所有基本类型都是固定大小的,并且对象的内部结构对程序员来说是不透明的。如果将此代码转换为Java,则可以通过固定值31替换sizeof(int) * CHAR_BIT - 1
在这个特定的代码中,它被用于计算int中的位数。请注意,此计算假定int类型不包含任何填充位。
假设您的编译器选择对有符号数的位移进行符号扩展,并且假设您的系统使用2s补码表示负数,这意味着对于正值或零值,mask将为0,对于负值,它将为-1。
要否定一个二进制补码数字,我们需要执行按位非,然后加一。等效地,我们可以减去一,然后按位取反。
再次假设二进制补码表示法,-1由所有的1表示,因此与-1异或相当于按位取反。
因此,当v为零时,数字保持不变,当v为1时,它被否定。
需要注意的是,在C和C++中,有符号溢出是未定义行为。因此,在最小负值上使用此abs实现会导致未定义的行为。这可以通过添加强制转换来解决,使得程序的最后一行在无符号int中评估。 *这通常但不一定与硬件可以寻址的最小内存单元相同。实现可能将多个可寻址的硬件内存单元合并为一个程序可寻址的内存单元,或将一个硬件可寻址的内存单元分成多个程序可寻址的内存单元。

然而,对于泛型来说,“仅仅将[...]替换为值31”并不那么容易。 - Alexis Wilke
如果你正在翻译成Java,那么真实情况是Java的泛型对于数值代码并不是很有用。 - plugwash

2

您应该知道,此代码依赖于有符号类型的右位移的实现定义行为。gcc承诺始终提供合理的行为(符号位扩展),但ISO C允许实现将上位比特填充为零。

解决此问题的一种方法:

#ifdef HAVE_SIGN_EXTENDING_BITSHIFT
int const mask = v >> sizeof(int) * CHAR_BIT - 1;
#else
int const mask = -((unsigned)v >> sizeof(int) * CHAR_BIT - 1);
#endif

您的Makefileconfig.h等文件可以根据您的平台在构建时定义HAVE_SIGN_EXTENDING_BITSHIFT


141
我不理解这怎么能成为被接受的答案,因为它没有回答问题,尽管是一个非常有趣的评论。 - qdii
22
@Mauris:有人编辑了这个问题,并将一个子问题提升为问题的标题。原来的标题确实很糟糕,但是提问者的问题是关于所引用的位操作代码如何工作的,“至少在可移植性方面它不起作用,这就是为什么”是一个有用的答案。 - R.. GitHub STOP HELPING ICE
13
我了解。不幸的是,即使这不是最初的问题,它在Google搜索结果中非常高地显示为“什么是CHAR_BIT?”根据你的解释,我理解你为什么写下这个答案,但对于后人来说,最好的方法可能要么是(a)移除你的答案并将其重写为问题的评论,以便@AraK的答案显示在顶部,要么是(b)编辑你的答案,以回答当前问题的标题。 - Lynn
1
由于原帖提问者的意图与编辑的解释之间存在差异,似乎原始请求的性质被无意中转移了。虽然两个问题(原始和编辑)都有价值,但这种差异需要得到解决。我现在询问:这个答案可以添加到维基上吗?这可能会帮助那些正在寻找此类信息的人,尽管它与原始问题无关。之后,问题���以再次编辑,以适应dato datuashvili的原始请求。只是一个关心的读者... - user6231921
4
我刚刚查看了这个问题的历史记录,实际上原始问题并没有问到代码是如何工作的。编辑升级为标题的那个问题才是唯一的实际问题。 - plugwash
1
注意:您可能会发现“符号扩展位移”也被称为“算术位移”(与“逻辑位移”相对)。 - wip

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