【问题标题】:Is left shifted short that is going to be stored in an int is UNDEFINED?将要存储在 int 中的左移短是未定义的吗?
【发布时间】:2020-07-16 19:56:02
【问题描述】:

我正在阅读一本实现自己动手 DNS 消息阅读器的书,它会尝试查看特定字段是否设置为 true。 书中使用的一段代码我看不懂。

const int qdcount = (msg[10] << 8) + msg[11];

msg 是 ==> char 类型(即 8 位)

qdcount ==> 应该是一个 16 位的字段,包含 DNS 查询的数量(由两个字段组成,msg[10]msg[11]

那么这段代码是如何工作的(例如,如果 msg[10] = 01001 0001)将其左移 8 应该会导致(1000 0000)即 UB,那么所做的任何计算都会导致错误的答案。假设msg[11] = 0010 1111。计算结果是 1000 0000 + 0010 1111 对吗?那么这行代码是如何工作的呢。

【问题讨论】:

  • 请不要描述msg的类型,而是显示声明。
  • 在移位完成之前,操作数被转换为int,因此10010001被转换为00000000 00000000 00000000 10010001(或11111111 11111111 11111111 10010001),然后才执行左移位
  • 参见Bitwise_shift_operators:“对两个操作数执行积分提升。”
  • @pmg 我不明白如果促销是自动完成的,这个操作什么时候会导致 UB。
  • 你机器中的普通char是有符号类型还是无符号类型?很重要

标签: c dns undefined-behavior bit-shift


【解决方案1】:

至少有两个独立的考虑因素。

  • char签名还是未签名

  • int 16 位或更宽?

    (msg[10] << 8) + msg[11];
    

大部分问题取决于:

E1 如果 E1 具有有符号类型和非负值,并且 E1 *2E2 是 可以在结果类型中表示,那么这就是结果值;否则,行为未定义。

char签名int 任意大小

msg[10] 被提升为 int 并左移 8。当值 msg[10] 为正时,这很好,而当为负时,未定义行为,因为左移负值是 UB。

char 无符号int 大于 16 位

msg[10] 提升为 int 并左移 8。这很好,所有 msg[10] 值都没有问题。

char无符号int 是 16 位

msg[10] 被提升为int 并左移 8。msg[10] &lt; 128 时这很好,否则 UB 移到符号位置 - 无法表示正值。


移位时最好使用无符号类型。

// char msg[100];
// const int qdcount = (msg[10] << 8) + msg[11];

char unsigned msg[100];
const unsigned qdcount = ((unsigned) msg[10] << 8) + msg[11];

【讨论】:

  • if char 将始终转换为 int 使用 unsigned char 或 signed char 有什么意义?它提供什么保护?。
  • @Khaled Gaber 当char 值为负时,左移为UB。使用unsigned char,提升为int 的值不是负数。只要左移不移入int符号位,一切都好。
  • @yes 现在知道了,我错过了符号是数字的最高位:)。
  • @KhaledGaber “整数提升在每个操作数上执行。”并不意味着该参数被提升为int。它也可以在所需的签名中提升为unsigned int 或更大的类型。请查看“整数提升”的主题以了解其含义。
  • @vlad_tepesch 详细信息:在移位运算符的情况下,操作数会经过通常的整数提升,如果比 int 窄,操作数会变为 intunsigned,但不会更广泛的类型。对于a shift bb 的类型不影响a 的类型。 int &lt;&lt; long 产生 intlong &lt;&lt; unsigned longlong :“结果的类型是提升的左操作数的类型。”
【解决方案2】:

来自C11 – ISO/IEC 9899:2011 draft 的第6.5.7.4 段链接here

[...] E1 如果 E1 具有有符号类型和非负值,并且 E1 × 2^E2 是 可以在结果类型中表示,那么这就是结果 价值;否则,行为未定义。

所以是的。左移有符号值可能会导致未定义的行为。

您的代码中实际上引起您困惑的另一件事是:

对每个操作数执行整数提升。

这意味着在移动操作数之前将扩展为int。因此,通过将位移入符号位(假设 sizeof(int)&gt;16),移位 8 不会导致溢出。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 2022-08-04
    相关资源
    最近更新 更多