在 c 中是否明确定义了在不同大小变量之间执行按位 OR 运算符并从较小的 uint 中减去较大的 uint 的行为?

Is behavior clearly defined in c for doing a bitwise OR operator between different size variables and subtracting a larger uint from a smaller uint?

提问人:Trashman 提问时间:5/3/2022 最后编辑:Trashman 更新时间:5/4/2022 访问量:104

问:

我试图弄清楚 c 中以下命令的最终结果是什么,以及可能更好的方法是什么。

这是我的变量。所有这些都是无符号整数,类型为 或uint16uint32

CNT_A:这是一个 *,但表示微处理器中的 16 位读/写计数器寄存器。它直接指向此寄存器的地址,因此它始终包含寄存器中的值。它由处理器自动递增,但可以由程序重置或写入设定值。从制造商的文档来看,上面的 16 位是“保留”的,因此,我相信,总是报告并且实际上不会被写入,所以它实际上是一个 16 位值。uint320

*NOTE: `CNT_A` is always called as a `uint32` in the code, but it is declared as a union of a `uint32` and a 32-bit `bitfield`, with the upper 16 bits of the bitfield unused.

update_counter:这是一个“易失性 uint16”,每当调用该函数时,它就会在另一个函数中递增。

detect_pulse如果在系统中检测到脉冲,则为 TRUEboolean

ignore_time是一个 const uint16

wTempA:这是从中提取 16 位计数器值的 auint16REG_A

lwTempB:这是一个 (=),用作存储两个 16 位值的临时变量,但涉及 32 位计算uint32llong

lwTempC:这是用于通过其他变量的差异来检测变化的uint32

'lwLastA':这是一个静态值,用于存储函数的最终值并将其带到函数的下一次调用,并与新的uint32lwTempAlwTempA

以下是函数中相关的实际代码片段(请注意,显示的注释是我为这篇文章添加的注释,而不是在实际文件中):

void changeHandler(void) {

    wTempA = REG_A;             //Assigning a uint32 to a uint16, but upper 16 bits of uint32 are always 0
    lwTempB = update_counter;   //Assigning a uint16 to a uint32
    lwTempB <<= 16;             //Shifting uint16 value to upper 16 bits
    lwTempB |= wTempA           //Doing a logical OR of a uint16 with a uint32, then storing back to that uint32
    ...
    if (detect_pulse)
    {
        lwLastA = wTempA            //Setting "previous" value to wTempA if a pulse was detected
    }

    lwtempC = lwTempB - lwLastA     //Subtracting uint16 from a uint32

    if (lwTempC <= ignore_time) //There must be a significant difference to perform the following actions
    {
          return;
    }

    //...perform important actions with micro... (redacted)

    ...

    lwLastA = wTempA            //Setting previous value of wTempA

}

我从以前的程序员那里继承了这个函数。这不是我会做的方式,但如果没有必要,我不想重写整个事情。

我认为,如果出现以下两种情况之一,该函数应该触发“重要操作”。一种是如果发生导致递增的外部事件,另一种是如果经过的时间超过 。update_counterignore_time

上述每条线的所有预期行为是否都明确定义,是否会产生一致的结果?

我有几个具体的担忧:

  1. 是否明确定义了 lwTempB (uint32) 和 wTempA (uint16) 之间的按位 OR 将比较较低的 16 位。

  2. update_counter将在经过一定时间后滚动(除非有问题,否则它计数的事件以设定的频率发生),在该滚动点,lwLastA 实际上将大于 lwTempB。这里是否有明确定义的行为,其中两个无符号整数以通常会产生负结果的方式减去,然后将其分配给无符号整数?从布尔数学来看,我认为这将导致 lwTempC 的值总是大于 ignore_time 因此仍然会导致触发,所以草率的编码仍然应该提供所需的结果吗?

C 逻辑运 赋值运算符

评论

1赞 Nate Eldredge 5/3/2022
您询问的是整数促销
3赞 Raymond Chen 5/3/2022
读取的“保留”通常意味着“值是不可预测的,应该被忽略”,而不是“读取为零”。这样,它们就可以在未来版本中获得意义,而不会破坏现有代码。
0赞 Nate Eldredge 5/4/2022
一个重要的问题:这个平台上的规模是多少?在 C 的几个提升规则中,给定类型是大于还是小于 是有区别的。即使你尝试使用显式大小的类型(如 、 等)编写所有代码,基本类型的详细信息仍然在后台。在少数情况下,这实际上会影响行为,无论如何,在解释规则如何导致发生的行为时,这很重要。intintuint16, uint32intlong int
1赞 Ian Abbott 5/4/2022
这种比较是正确的方法吗?lwTempC <= ignore_time
0赞 Trashman 5/4/2022
@NateEldredge好点。该项目使用使用 ILP32 数据模型的 32 位处理器。uint16 和 uint32 在头文件中定义如下:typedef unsigned long uint32 ; typedef unsigned short uint16 ; typedef unsigned char uint8 ;

答:

-1赞 gnasher729 5/4/2022 #1

实现定义的行为。这意味着,C 语言不定义行为,但您的实现(编译器和标准库)必须定义。

在整数运算中,两端都提升为 int、unsigned int、long 或 unsigned long,具体取决于编译器给出的规则。现在涉及的类型之一是“无符号空头”。在 16 位系统上,short 和 int 都可以是 16 位,因此 unsigned short 不能“提升”为 int,而是 unsigned int。在 32 位系统上,int 将为 32 位,无符号短线将提升为 int。因此,可以使用无符号 int 或 int 操作数执行操作,这有所不同。特别是如果你比较数字。

打开所有可用的警告,希望您会被告知不良行为。

评论

0赞 Eric Postpischil 5/4/2022
Re “在整数运算中,两端都提升为 int、unsigned int、long 或 unsigned long,具体取决于编译器给出的规则”:(a) 这被过度简化,以至于可能导致错误的结论和代码中的错误。(b) 规则由 C 标准设置,尽管它们依赖于实现定义的某些类型属性。(c) 大多数二进制算术运算符使用通常的算术转换,而不是整数运算。整数升级不会更改值;转换可能...
0赞 Eric Postpischil 5/4/2022
...(d) 操作数可以转换为比其中一个操作数更宽的类型更宽的类型。(e) 类型的宽度由C实现决定,而不是由目标是“16位”还是“32位”决定。没有定义“16 位”或“32 位”系统的单一属性;系统具有多种具有不同宽度的功能:总线宽度、处理器寄存器宽度、指令操作数等。C 实现通常受目标属性的影响,但可能出于其他原因选择其整数宽度。unsigned long