【问题标题】:Extract upper and lower word of an unsigned 32-bit integer提取无符号 32 位整数的高位和低位字
【发布时间】:2019-05-21 19:36:11
【问题描述】:

要提取无符号 32 位整数的高位和低位字并将每个字存储在单独的 uint16_t 变量中,我执行以下操作(nbr 是无符号 32 位整数):

uint16_t lower_word = (uint16_t) nbr & 0x0000FFFF;  // mask desired word
uint16_t upper_word = (uint16_t) ((nbr & 0xFFFF0000) >> 16); // right-shift after masking

是否不需要显式转换为 uint16_t?如果有任何其他更有效的方法,您是否建议获得所需的结果而不是这种方法?

【问题讨论】:

    标签: c word uint32 uint16


    【解决方案1】:

    不,您不需要类型转换,我建议不要使用类型转换。这是因为它的优先级高于 & 运算符,所以 nbr 先转换为 uint16_t 再屏蔽。这也是为什么如果没有额外的括号,第二行将无法工作。

    除此之外,代码还不错,没有真正的理由使用不同的方法。您也可以先进行移位,然后屏蔽该值,但生成的汇编代码应该完全相同。

    【讨论】:

    • @AbdelAleem 一旦发布了答案,请不要“修复”问题。而是修复实际代码:) 我将回滚您的更改,因为它使发布的答案无关紧要。
    • 我不同意,需要类型转换,因为他想要 16 位的结果,当然代码必须在没有警告的情况下编译
    • 代码编译时不会出现警告,至少在 gcc 8.2 上,即使您使用 -Wall -Wpedantic -Wextra。也就是说,强制转换也不会受到伤害,至少在尊重运算符优先级的情况下(因此应始终使用额外的括号)
    • @bruno 我完全同意你的观点,我实际上在我的所有代码上都使用了 -Wall -Wextra。这就是为什么我做了一个快速测试,发现如果省略强制转换,gcc 不会抱怨,即使我添加 -Wpedantic
    • gcc 有一个名为-Wconversion 的潜在危险类型转换的单独警告。它不包含在-Wall-Wextra 中,可能是因为它会产生大量误报并发现真正的错误(如静态分析器)。 -pedantic 只影响标准合规性,uint16_t u16 = u32 >> 16; 就标准而言没问题。
    【解决方案2】:
    uint16_t lower_word = (uint16_t) nbr;
    uint16_t upper_word = (uint16_t) (nbr  >> 16);
    

    面具没用

    强制转换是必要的,否则编译器可能会产生警告

    {编辑以考虑 Lundin / Eric Postpischil 的评论}

    例如 gcc -Wconversion 会在没有演员表的情况下产生警告

    【讨论】:

    • 我明白你的逻辑了。屏蔽似乎是不必要的。
    • 赋值不违反 C 标准中的任何约束,因此编译器不需要产生警告,而且大多数默认情况下不会产生警告,即使请求“所有”正常警告也是如此。例如,GCC 仅在明确请求“-Wconversion”时发出警告;此警告不在其“-Wall”警告集中。
    • @EricPostpischil 是的,Lundin 在下一个答案的评论中警告过我(:)),但谢谢
    【解决方案3】:

    C 类型系统既微妙又危险。显式转换可能需要也可能不需要。在(uint16_t) nbr & 0x0000FFFF 的情况下,假设是 32 位 CPU,转换不正确。

    您在操作发生之前施放。这意味着操作数 nbr 将通过强制转换显式转换,然后通过隐式整数提升立即隐式转换为 int。结果将是int 类型,它已签名。在这种情况下无害,但在其他情况下可能会造成麻烦。通过使用不正确的演员阵容,您从uint32_t 中制作了signed int,这不是本意。

    总的来说你需要注意Implicit type promotion rules

    虽然,在分配回uint16_t 时存在隐式左值转换,这在大多数情况下可以节省时间。

    还要注意0x0000FFFF 是危险的风格。十六进制文字是适合该值的类型,无论您在值之前放置了多少个零。在这种情况下,签名的是int。在 16 位系统上,0x0000FFFF 会给出int,但0x00008000 会给出unsigned int。 (例如检查这个奇怪的错误:Why is 0 < -0x80000000?

    最佳实践、坚固、可移植、符合 MISRA-C 的代码是完全不包含任何隐式转换的代码:

    uint32_t nbr = ...;
    uint16_t lower_word = (uint16_t) (nbr & 0xFFFFUL);
    uint16_t upper_word = (uint16_t) ((nbr >> 16) & 0xFFFFUL);
    

    假设nbr 已知为uint32_t,否则最好在转换前将该操作数转换为uint32_t

    在这种特定情况下,掩码并不是真正需要的,但在一般情况下,例如从 uint32_t 屏蔽掉 4 个字节时。

    【讨论】:

    • @EricPostpischil 正如最初所述:“假设 32 位 CPU”。显然,如果 intlong 在某些车库编译器上是 64 位的,则将 UL 交换为 U。说真的,除了跟踪我发布的每一个帖子并附带远远超出任何新手需要知道的语言律师评论之外,你没有什么比这更好的事情了吗?这个答案已经太详细了,无法进行教学,没有涵盖对晦涩的车库编译器的可移植性。
    【解决方案4】:

    如果您需要在代码中多次重复此操作,还有另一种使用联合的方法:

    typedef union _uplow                                                                                     
    {                                                                                                        
      struct _reg {                                                                                          
        uint32_t low : 16;                                                                                   
        uint32_t up  : 16;                                                                                   
      } reg;                                                                                                 
      uint32_t word;                                                                                         
    } uplow;
    

    如下声明你的变量:

    uplow my_var;
    my_var.word = nbr;
    

    像这样使用它:

    printf ("Word : 0x%x\n Low : 0x%x\n Up  : 0x%x\n", my_var.word, my_var.reg.low, my_var.reg.up);
    

    输出:

    Word : 0xaaaabbbb
     Low : 0xbbbb
     Up  : 0xaaaa
    

    【讨论】:

    • 这是非常不可移植的,特定于编译器和特定于 CPU。虽然 OP 的代码是 100% 可移植的,而且比这要好得多。您可以使用 uint16_t[2]uint32_t 之间的联合来减少问题,但您仍然会不必要地依赖字节序。
    • @Lundin 是的,同时也很复杂。我对这个答案投了反对票,就像我使用指针对现在删除的答案所做的那样,这两个反对票是我在 S.O. 上唯一投反对票的。但很难不做:(
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 2011-03-09
    • 1970-01-01
    • 2017-04-19
    相关资源
    最近更新 更多