【问题标题】:Should a cast be used to truncate a long variable?是否应该使用强制转换来截断长变量?
【发布时间】:2019-02-25 12:41:34
【问题描述】:

我有一个 16 位无符号变量。我需要将它分成 8 位块。

以下做得够了吗:

chunk_lsb = (uint8)variable;
chunk_msb = (uint8)(variable >> 8);

或者我应该使用面具:

chunk_lsb = (uint8)(variable & 0xFFu);
chunk_msb = (uint8)((variable >> 8) & 0xFFu);

我知道这两种方法都有效,我只是在寻找最好的方法,如果有的话。也许没有,只是使用演员表来减少计算是最好的方法?大家觉得呢?

【问题讨论】:

  • 我认为两者都会生成相同的二进制代码。由于第一个解决方案更具可读性,我会使用这个。您只需要确保 uint8 是 8,但在所有平台上(我猜是)
  • 谢谢@jaudo,这也是我的方向。
  • 旁注:应该首选标准stdint.h 类型,例如uint8_t,而不是自己创建。

标签: c casting mask truncate


【解决方案1】:

不清楚variable 是什么类型。如果没有指定,我们只能推测。

但一般来说,您应该避免对有符号整数类型进行位移,因为这会导致各种形式的定义不明确的行为。这反过来意味着您也必须小心小整数类型,因为它们会被提升为签名int。见Implicit type promotion rules

如果variable 未签名,则(uint8)((variable >> 8) & 0xFFu); 的特定情况是安全的。否则它是不安全的,因为右移一个负值会导致实现定义的行为(算术或逻辑移位)。

variable << 8 将在 16 位系统上调用未定义的行为,以防 variable 是小整数类型或 int16_t

因此,无论左移/右移,最安全、最便携的方式是这样的:

chunk_lsb = variable;
chunk_msb = ((unsigned int)variable >> 8);

虽然您可能希望过于明确以消除所有编译器警告:

chunk_lsb = (uint8_t) (variable & 0xFFu);
chunk_msb = (uint8_t) ( (unsigned int)variable>>8 & 0xFFu );

【讨论】:

  • AFAICT (variable >> 8) & 0xFFu 将给出相同的结果,无论移位是算术还是逻辑移位,只要 int 至少为 16 位宽(我相信标准保证它是) .
  • @IlmariKaronen 变量的大小无关紧要,只有符号。如果值为负,您将获得实现定义的行为。这种损坏代码的一个例子是unsigned char u = 0; ... ~u >> n;
  • 我同意这是不可预测的,因为标准不要求实现定义的负符号整数右移结果为 either 算术 逻辑转变。然而,在它 这两者之一的实现中,右移 n 位,然后屏蔽(至少)n 最高位无论是哪种转变,都应该产生相同的结果。因此,您在答案第三段中的括号内的评论似乎有点误导。
  • @Lundin,可能值得注意的是,您的第一组建议假定chunk_lsbchunk_msb 本身具有与uint8_t 等效的类型。这个问题并不清楚——OP的“8位块”可以存储在具有更广泛类型的变量或带符号的变量中。
  • 不同意使用(uint8_t) 转换 chunk_lsb = (uint8_t) (variable & 0xFFu); 中的掩码。演员阵容总是足以消除警告。掩码是更好的编程。但两者都是WET。除此之外,我真的很喜欢这个答案。
【解决方案2】:

由于uint8 未签名,您没有 进行屏蔽:

6.3.1.3 有符号和无符号整数

  1. 当一个整数类型的值被转换为另一个整数类型而不是 _ Bool 时,如果该值 可以用新类型来表示,它是不变的。
  2. 否则,如果新类型是无符号的,则通过重复加减来转换值 比新类型可以表示的最大值大一,直到该值在 新类型的范围。 60)
  3. 否则,新类型是有符号的,值不能在其中表示;结果要么是 引发了实现定义或实现定义的信号。

但是,很可能两者都会产生相同的编译器输出。我通常会添加掩码,因为它可以明确代码应该做什么,并且不需要强制转换。

【讨论】:

  • 除了,C 有讨厌的整数提升,所以如果你尝试做同样的事情并左移,程序可能会在未定义的行为中内爆。在这种特殊情况下,我们逃脱了,因为它是正数的右移。
  • 此外,演员表使精度损失明显是故意的。许多编译器会在精度损失时发出警告,这非常重要。 (我在想那些非常确定以至于“size_t 和指针实际上只是 unsigned int...)的菜鸟
  • @Lundin:在 C89 下,为整数类型没有填充位或陷阱表示的实现上的所有可能的左操作数值定义了带符号的左移行为,但陷阱可能更明智在强制行为不同于二次方乘法的情况下。我没有看到任何证据表明有任何意图改变对 C89 的行为和二次幂乘法等效的情况的处理。
  • @supercat 不管 UB 和签名格式如何,不小心将数据移入符号位已经够糟糕了。
  • @Lundin:在二进制补码表示法中,符号位只是“该位的状态和左侧无限位数”的简写。值 -3 是后跟 01 的无限个 1。将 32 位二进制补码值视为由无限个 0 或 1 后跟正好 31 个位组成可能更方便,但是无限个 1,后跟 29 个,然后是 01,与无限个 1 后跟 01 相同。
【解决方案3】:

是否应该使用强制转换来截断长变量?

如果 chunk_lsb 是 8 位对象(比 variable 窄),请使用强制转换或掩码(不能同时使用两者)。有助于消除有关范围减小的迂腐警告。我更喜欢掩码——除非编译器很挑剔。

uint8_t chunk_lsb = (uint8_t) variable;
// or 
uint8_t chunk_lsb = variable & 0xFFu;

否则使用掩码。

unsigned chunk_lsb = variable & 0xFFu;

【讨论】:

  • 这和你关于同时使用 WET 解决方案的评论很令人满意,谢谢@chux
【解决方案4】:

也许没有,只是使用强制转换来减少计算是最好的方法?

一般来说,asm 码都是一样的,所以速度方面,你用哪一个都无所谓:

你们觉得呢?

IMO,就可读性而言,第一个更清晰,但我无法找出支持我偏好的编码标准或指南。无论如何,如果您的偏好是第二个,我会使用 const 变量来删除幻数并更清楚地表明目的是屏蔽(假设您为 const 变量选择了正确的名称)。

【讨论】:

  • 无论如何,如果您的偏好是第二个,我会使用 const 变量来删除幻数并更清楚地表明目的是屏蔽任何认为 @987654325 的人@ 在位掩码的上下文中是一个“神奇的数字”,必须用一个变量来掩盖它正在从事货物崇拜编程。有什么可能的变量比0xFF 作为位掩码更清晰? one_byte_bit_mask_0xFF?
  • IMO,如果目的是为了掩饰,那么掩码这个词比任何(魔术)数字都清晰。
  • IMO,如果目的是掩盖,那么面具这个词比任何(魔术)数字都清晰 哦?这个屏蔽了多少位:unsigned int result = input & mask; 不,我没有 DV。
  • 0xFF vs one_byte_bit_mask_0xFF vs mask,我选择第二个作为最差的选项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-07
  • 2014-02-16
  • 1970-01-01
  • 2012-02-18
  • 2019-02-17
  • 1970-01-01
相关资源
最近更新 更多