为啥C没有二进制文字?
Posted
技术标签:
【中文标题】为啥C没有二进制文字?【英文标题】:Why doesn't C have binary literals?为什么C没有二进制文字? 【发布时间】:2013-08-17 04:08:30 【问题描述】:我经常希望我能在 c: 中做这样的事情:
val1 &= 0b00001111; //clear high nibble
val2 |= 0b01000000; //set bit 7
val3 &= ~0b00010000; //clear bit 5
拥有这种语法似乎是对 C 的一个非常有用的补充,没有我能想到的任何缺点,而且对于位旋转相当普遍的低级语言来说,这似乎是一件很自然的事情。
编辑:我看到了其他一些很棒的选择,但是当有更复杂的面具时,它们都会崩溃。例如,如果reg
是控制微控制器上的 I/O 引脚的寄存器,并且我想同时将引脚 2、3 和 7 设置为高电平,我可以写入 reg = 0x46;
,但我不得不花费 10 秒考虑一下(在我一两天不看这些代码后,每次阅读这些代码时,我可能不得不再花 10 秒钟)或者我可以写reg = (1 << 1) | (1 << 2) | (1 << 6);
,但我个人认为这比只写`reg = 0b01000110;'我可以同意它不能很好地扩展到 8 位或 16 位架构之外。并不是说我需要制作 32 位掩码。
【问题讨论】:
它有十六进制,如果你花 10 分钟来感受一下这段关系,我认为它会更好 C 有“二进制”文字,但只有 2 个:0、1。;-) 物有所值,C++14 will have these。#define B00000000 0
#define B00000001 1
#define B00000010 2
#define B00000011 3
... #define B10100100 0xA4
... 可能会转换为unsigned char
。
@AnT:更准确地说,0
是一个八进制常数
【参考方案1】:
根据Rationale for International Standard - Programming Languages C §6.4.4.1 整数常量
由于缺乏先例和实用性不足而拒绝了添加二进制常量的提议。
它不在标准 C 中,但 GCC 作为扩展支持它,前缀为 0b
或 0B
:
i = 0b101010;
详情请见here。
【讨论】:
太棒了,但我没有使用 GCC :( 你知道标准中没有什么吗? @Drew 查看更新。换句话说,委员会认为它的使用可以被十六进制常量覆盖,我认为。 有时我认为编写标准的人实际上并没有用他们正在标准化的语言编写任何代码。那,或者它们都是同一个利基技术空间的一部分,并且无法理解任何人将该语言用于他们使用它的其他任何用途。【参考方案2】:这就是推动hexadecimal 成为...十六进制的原因。 “...十六进制表示法的主要用途是在计算和数字电子设备中对二进制编码值的人类友好表示...”。如下:
val1 |= 0xF;
val2 &= 0x40;
val3 |= ~0x10;
十六进制:
-
一个十六进制数字可以表示一个半字节(4 位或半个八进制)。
两个十六进制数字可以代表一个字节(8 位)。
当缩放到更大的蒙版时,Hex 更加紧凑。
通过一些练习,十六进制和二进制之间的转换会变得更加自然。尝试手动写出您的转换,而不是使用在线二进制/十六进制符号转换器 - 然后几天后它就会变得自然(并且因此更快)。
旁白:尽管二进制文字不是 C 标准,但如果您使用 GCC 编译,则可以使用二进制文字,它们应该以 '0b' 或 '0B' 为前缀。有关详细信息,请参阅官方文档here。示例:
int b1 = 0b1001; // => 9
int b2 = 0B1001; // => 9
【讨论】:
是的,这就是我最终会做的事情,但我总是必须在我的计算机中进行大量计算才能记住二进制文件在 hax 中的含义。特别是如果我想例如。清除最低 6 位。而且我同意二进制文字对于 32 位平台来说会很长,但在这种情况下你不能使用它们。 经过一些练习后,以十六进制思考成为第二天性。十六进制还有一个优点,就是比二进制更容易阅读。 @Drew,我理解您的观点,因为在视觉上考虑较小的面具可能更容易。一旦你练习得足够多,它就会变得相当自然(就像生活中的一切一样)。我建议在创建掩码时手动进行所有计算并在计算器上仔细检查自己,以便您可以更好地在两种符号之间进行转换。 想象一个十六进制数字表示的二进制值真的需要多少心算?在优秀程序员需要在头脑中进行的所有操作/计算中,我认为这是最简单的一种。 请将要点 1 中的“4 字节”更改为“4 位”。一个半字节是 4 位,而不是字节。【参考方案3】:你所有的例子都可以写得更清楚:
val1 &= (1 << 4) - 1; //clear high nibble
val2 |= (1 << 6); //set bit 6
val3 &=~(1 << 3); //clear bit 3
(我冒昧地将 cmets 固定为从零开始计数,就像 Nature 所期望的那样。)
您的编译器将折叠这些常量,因此以这种方式编写它们不会影响性能。而且这些版本比0b...
版本更容易阅读。
【讨论】:
@Jerry 好吧,这将教会我在第一个错误后不要停止思考。谢谢 如果我们考虑字节序,(1 << 4) - 1
真的和0xF
一样吗? ...也许不是。
@AbrahamSanchez:是的,在所有平台上都是一样的。像左移这样的算术运算是独立于字节序定义的。 (事实上,除非你强制转换指针或使用联合,否则甚至无法检测到字节序。)
@Nemo 或数组、结构或任何其他变量。只要你使用一些像 memcpy 这样的 hack,一切皆有可能。
@YoYoYonnY:正如我所说,所有这些都要求您“投射指针或使用联合”。【参考方案4】:
我认为可读性是主要问题。虽然是低级的,但阅读和维护您的代码的是人,而不是机器。
您是否容易发现错误输入了0b1000000000000000000000000000000(0x40000000)
,而您的真正意思是0b10000000000000000000000000000000(0x80000000)
?
【讨论】:
这似乎是迄今为止最好的理由。不过,在这些情况下,您不必使用二进制文件。你多久制作一次 32 位掩码? 由于在大多数情况下他们有更好的选择(十六进制),我猜委员会只是通过不提供来关闭此类错误的大门。 但这使得其他一些情况更难理解,例如,如果这是一个位可能具有不同含义的寄存器,则必须取出计算器来查看启用/禁用的位只是一个并发症。这是处理 i2c/spi 传感器时的常见情况。 @lesto,这就是最常使用十六进制形式的地方。很容易从十六进制中分辨出二进制代码。 仅仅因为你可以用一个特性编写不可读的代码,这不是不允许它的理由。如果你愿意,你总是可以用每种语言编写不可读的代码。【参考方案5】:“例如,如果 reg 是控制微控制器上的 I/O 引脚的寄存器”
我不禁认为这是一个不好的例子。控制寄存器中的位具有特定的功能(连接到各个 IO 位的任何设备也一样)。
在头文件中为位模式提供符号常量比在代码中计算出二进制文件要明智得多。将二进制转换为十六进制或八进制是微不足道的,记住将 01000110 写入 IO 寄存器时会发生什么并不重要,尤其是在您手边没有数据表或电路图的情况下。
您不仅可以节省 10 秒尝试计算二进制代码的时间,还可以节省一些尝试计算二进制代码的时间!
【讨论】:
【参考方案6】:为此,我建议在 C 中使用 C 宏以避免编译器警告或其他问题。我使用 Ox 而不是 0x(就像在“Ohio”中一样)。
#define Ob00000001 1
#define Ob10000000 (1 << (8-1))
#define Ob00001111 15
#define Ob11110000_8 (Ob00001111 << (8 - 4))
#define Ob11110000_16 (Ob00001111 << (16 - 4))
#define Ob11110000_32 (((uint32_t) Ob00001111) << (32 - 4))
#define Ob11110000_64 (((uint64_t) Ob00001111) << (64 - 4))
#define Ox0F Ob00001111
#define OxF0 Ob11110000_8
#define OxF000 Ob11110000_16
#define OxF0000000 Ob11110000_32
#define OxF000000000000000 Ob11110000_64
int main()
#define Ob00001110 14
// bitwise operations work
if (Ob00001110 == (Ob00001111 & ~Ob00000001))
printf("true\n");
【讨论】:
【参考方案7】:在控制器上设置特定输出时,二进制最有用。我使用的 hack 在技术上是非法的,但仍然有效。如果您只需要打开一个 LED,它就会冒犯使用整个 int 甚至是 char 来完成工作的所有敏感性。不要忘记我们可能不是在谈论这些东西的终极编译复杂性。因此,对于结合组控制的个人可理解性,我使用位域:-
struct DEMAND
unsigned int dOil : 1; // oil on
unsigned int dAir : 1; // air on
unsigned int dHeat : 1; // heater on
unsigned int dMtr1 : 1; // motor 1 on
unsigned int dMtr2 : 1; // motor 2 on
unsigned int dPad1 : 10;// spare demand o/p's
unsigned int dRunCycle: 1; // GO !!!!
unsigned int dPad2 : 15;// spare o/p's
unsigned int dPowerOn: 1; // Power on
DemandBF;
它们在单独使用时很容易解决,或者为了更彻底的控制,它们可以被视为无符号整数,公然无视 K&R:-
void *bitfPt = &DemandBF;
unsigned int *GroupOuts = (unsigned int *)bitfPt;
DemandBF.dAir = 1; // Clearly describes what's turning on
DemandBF.dPowerOn = 1;
*GroupOuts ^= 0x04; // toggle the heater
*GroupOuts = 0; // kill it all
它一直对我有用,它可能不是便携式的,但是无论如何谁真正移植了这样的东西?试一试。
【讨论】:
" 但是谁真正移植了这样的东西呢?"为相同硬件更改编译器的人。大约 100% 的嵌入式系统程序员在其职业生涯的某个阶段都必须这样做。【参考方案8】:我的做法是:
/* binmacro.h */
#define BX_0000 0
#define BX_0001 1
#define BX_0010 2
#define BX_0011 3
#define BX_0100 4
#define BX_0101 5
#define BX_0110 6
#define BX_0111 7
#define BX_1000 8
#define BX_1001 9
#define BX_1010 A
#define BX_1011 B
#define BX_1100 C
#define BX_1101 D
#define BX_1110 E
#define BX_1111 F
#define BIN_A(x) BX_ ## x
#define BIN_B(x,y) 0x ## x ## y
#define BIN_C(x,y) BIN_B(x,y)
#define BIN_B4(x,y,z,t) 0x ## x ## y ## z ## t
#define BIN_C4(x,y,z,t) BIN_B4(x,y,z,t)
#define BIN(x,y) BIN_C(BIN_A(x),BIN_A(y))
#define BIN4(x,y,z,t) BIN_C4(BIN_A(x),BIN_A(y),BIN_A(z),BIN_A(t))
/*---- test ... ---*/
BIN(1101,0100)
BIN4(1101,0010,1100,0101)
哪些预处理...
$ cpp binmacro.h
0xD4
0xD2C5
【讨论】:
【参考方案9】:以下内容仅限于 8 位,但应该可以直接扩展。虽然它不会产生 C 文字,但会产生编译时间常数。
#define B_(X) B8_("00000000" #X)
#define B8_(X) B8__(X+sizeof(X)-9)
#define B8__(X) \
(B___((X), 7) | B___((X), 6) | B___((X), 5) | B___((X), 4) | \
B___((X), 3) | B___((X), 2) | B___((X), 1) | B___((X), 0))
#define B___(X, I) (((X)[7-(I)] - '0') << (I))
下面的函数被编译成返回常量18
的代码。
int test(void)
return B_(10010);
Try it online!
如果性能不是问题,您可以做一些更简单的事情:
#define B_(x) strtoull(#x, 0, 2)
【讨论】:
这将在每次运行此代码时执行运行时字符串解码三次。这对性能没有好处。 @DavidGiven,编译器将值优化为编译时常量是微不足道的。不执行运行时解码。以上是关于为啥C没有二进制文字?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在严格模式下不允许使用八进制数字文字(以及解决方法是啥?)
请问php中$c=(-3)^3; echo $c;会输出多少?为啥?