提取无符号32位整数的大写和小写字母

5
为了提取无符号32位整数的高16位和低16位,并将它们分别存储在不同的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是否是不必要的?如果有其他更有效的方法来获得所需的结果,您会推荐什么方法而不是这种方法吗?

4个回答

8
C类型系统既微妙又危险。显式转换可能是必要的,也可能不是必要的。特别是在32位CPU的情况下,对于(uint16_t) nbr & 0x0000FFFF,强制转换是不正确的。在操作发生之前进行转换,意味着操作数nbr将通过强制转换得到显式转换,并立即通过隐式整数提升隐式转换为int。结果将是有符号的int,在这种情况下无害,但在其他情况下可能会引起麻烦。通过使用不正确的转换,您将一个本不意图成为signed intuint32_t变成了signed int
总体而言,您需要注意隐式类型提升规则
尽管如此,在分配回uint16_t时存在隐式左值转换,这在大多数情况下能够解决问题。

请注意,0x0000FFFF 这种写法很危险。十六进制字面量的类型可以适应值,无论在值前面有多少个零。在这种情况下,它是有符号整数 int。在一个 16 位系统中,0x0000FFFF 得到的是 int,但是 0x00008000 得到的是 unsigned int。(例如,请查看这个怪异的错误: 为什么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。说真的,你难道没有更好的事情可做吗?每次我发帖子时都跟踪我并提出远远超出任何新手需要了解的语言法律评论?这个答案已经过于详细,无法作为教学内容,也没有涵盖到模糊的车库编译器的可移植性问题。 - Lundin

6
uint16_t lower_word = (uint16_t) nbr;
uint16_t upper_word = (uint16_t) (nbr  >> 16);

口罩是无用的

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

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

例如,gcc -Wconversion没有转换时会产生警告


我理解你的逻辑。遮罩似乎是不必要的。 - Abdel Aleem
赋值操作不违反C标准中的任何限制,因此编译器不需要产生警告,大多数编译器即使请求“所有”常规警告时,默认情况下也不会产生警告。例如,GCC仅在明确请求“-Wconversion”时发出警告;这个警告不在其“-Wall”警告集合中。 - Eric Postpischil
@EricPostpischil 是的,Lundin在下一个答案的评论中警告了我(:)),但还是谢谢您。 - bruno

2
不需要类型转换,我建议不要使用它。这是因为它的优先级比&运算符高,所以nbr首先被转换为uint16_t,然后被屏蔽。这也是为什么第二行没有额外括号将无法工作的原因。
除此之外,代码完全正常,没有使用不同的方法的真正原因。你也可以先进行移位,然后屏蔽值,但生成的汇编代码应该完全相同。

1
@AbdelAleem 请不要在有答案发布后“修复”问题。相反,请修复实际的代码 :) 我会回滚您的更改,因为它会使发布的答案变得无关。 - Lundin
@FelixG -Wall 对我来说至少是强制编译的,编译器不会产生警告/错误是因为它不友好,而是因为这有帮助,程序员必须考虑它们 :) - bruno
1
@bruno 我完全同意你的看法,而且我实际上在我的所有代码中都使用 -Wall -Wextra。这就是为什么我进行了快速测试,并发现如果省略强制转换,即使我添加 -Wpedantic,gcc也不会抱怨。 - Felix G
@FelixG 哦,是的,太奇怪了! - bruno
1
gcc有一个单独的警告选项 -Wconversion,用于潜在危险类型转换。它不包含在 -Wall-Wextra 中,可能是因为它会与发现真正错误(如静态分析器)一起产生大量误报。-pedantic 只影响标准合规性,uint16_t u16 = u32 >> 16; 在标准允许的范围内是可以的。 - Lundin
显示剩余2条评论

-1
如果您需要在代码中多次重复执行此操作,则可以使用联合的另一种方法来实现:
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

1
这很不可移植,既依赖于编译器又依赖于 CPU。虽然 OP 的代码是 100% 可移植的,比这好得多。你可以在 uint16_t[2] 和 uint32_t 之间使用 union 来减少问题,但仍然会不必要地依赖字节序。 - Lundin
@Lundin 是的,同时也很复杂。我踩了这个答案,就像我之前踩那个使用指针的已删除答案一样,这两个踩是我在 S.O. 上唯一的踩,但很难不这么做 :( - bruno

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