捍卫" U"十六进制文字

时间:2017-08-10 18:26:58

标签: c microcontroller

我和我的同事之间就以十六进制表示的文字后的U后缀存在争议。请注意,这不是关于此后缀的含义或其后果的问题。我在这里找到了几个主题,但我没有找到问题的答案。

一些背景信息:

我们试图达成一套我们都同意的规则,从那时起就将其作为我们的风格。我们有一份 2004 Misra C 规则的副本,并决定将其作为起点。我们对完全符合Misra C标准不感兴趣;我们正在挑选我们认为最能提高效率和稳健性的规则。

上述指南中的规则10.6规定:

  

“U”后缀应适用于所有无符号类型的常量。

我个人认为这是一个很好的规则。它只需要很少的努力,看起来比显式转换更好,并且更明确地显示了常量的意图。对我而言,将它用于所有无符号的内容是有意义的,而不仅仅是数字,因为通过允许异常来强制执行规则,特别是对于常用的常量表示。

然而,我的同事认为十六进制表示不需要后缀。主要是因为我们几乎只使用它来设置微控制器寄存器,并且在将寄存器设置为十六进制常量时,符号性并不重要。

我的问题

我的问题不在于谁是对还是错。它是关于确定是否存在后缀的缺失或存在改变操作结果的情况。有没有这样的情况,还是一致的问题?

编辑:澄清;特别是关于通过为它们分配十六进制值来设置微控制器寄存器。是否会出现后缀可能会产生影响的情况?我觉得它不会。例如,飞思卡尔处理器专家将所有寄存器分配生成为无符号。

3 个答案:

答案 0 :(得分:10)

为所有十六进制常量附加U后缀会使它们无符号,如您所述。当这些常数与有符号值一起用于操作时,这可能会产生不良副作用,尤其是比较。

这是一个病态的例子:

#define MY_INT_MAX  0x7FFFFFFFU   // blindly applying the rule

if (-1 < MY_INT_MAX) {
    printf("OK\n");
} else {
    printf("OOPS!\n");
}

已经精确指定了有符号/无符号转换的C规则,但有点违反直觉,因此上述代码确实会打印OOPS

MISRA-C规则是精确的,因为它表示 A“U”后缀应该应用于所有无符号类型的常量。 无符号字具有深远的影响,事实上,大多数常数不应该被认为是无符号的。

此外,C标准在十进制和十六进制常量之间产生了微妙的差异:

  • 如果十六进制常量的值可以用无符号整数类型表示,而不是类型int及更大类型的相同大小的有符号整数类型,则认为十六进制常量是无符号的。

这意味着在32位2的补码系统中,2147483648longlong long0x80000000unsigned int 。在这种情况下,附加U后缀可能会更明确,但避免潜在问题的真正预防措施是要求编译器完全拒绝签名/未签名的比较:gcc -Wall -Wextra -Werrorclang -Weverything -Werror是生命保护程序。

这是多么糟糕的事情:

if (-1 < 0x8000) {
    printf("OK\n");
} else {
    printf("OOPS!\n");
}

上述代码应在32位系统上打印OK,在16位系统上打印OOPS。更糟糕的是,看到嵌入式项目使用过时的编译器仍然很常见,这些编译器甚至没有为此问题实现标准语义。

对于您的具体问题,微处理器寄存器的定义值专门用于通过赋值设置它们(假设这些寄存器是存储器映射的),根本不需要U后缀。寄存器左值应该是无符号类型,十六进制值将根据其值进行有符号或无符号,但操作将继续相同。用于设置有符号数或无符号数的操作码在目标体系结构和我见过的任何体系结构上都是相同的。

答案 1 :(得分:1)

使用所有整数常量

附加u/U确保整数常量将是某些无符号类型。

没有u/U

  1. 对于十进制常量整数常量将是一些签名类型。

  2. 对于十六进制/八进制常量整数常量签名无符号类型,取决于值和整数类型范围。

  3. 注意:所有整数常量都有正值。

    //      +-------- unary operator
    //      |+-+----- integer-constant
    int x = -123;
    
      

    缺少或是否存在后缀会改变操作的结果?

    什么时候这很重要?

    使用各种表达式,需要控制数学的符号和宽度,并且最好不要惊讶。

    // Examples: assume 32-bit `unsigned`, `long`, 64-bit `long long`
    
    // Bad       signed int overflow (UB)
    unsigned a = 4000 * 1000 * 1000;  
    // OK
    unsigned b = 4000u * 1000 * 1000;  
    
    // undefined behavior
    unsigned c = 1 << 31
    // OK
    unsigned d = 1u << 31
    
    printf("Size %zu\n", sizeof(0xFFFFFFFF));  // 8  type is `long long`
    printf("Size %zu\n", sizeof(0xFFFFFFFFu)); // 4  type is `unsigned`
    
    //              2 ** 63
    long long e = -9223372036854775808;     // C99: bad "9223372036854775808" not representable
    long long f = -9223372036854775807 - 1; // ok 
    long long g = -9223372036854775808u;    // implementation defined behavior **
    
    some_unsigned_type h_max = -1;  OK, max value for the target type.
    some_unsigned_type i_max = -1u; OK, but not max value for wide unsigned types
    
    // when negating a negative `int`
    unsigned j = 0  - INT_MIN;  // typically int overflow or UB
    unsigned k = 0u - INT_MIN;  // Never UB
    

    **或引发实现定义的信号。

答案 2 :(得分:0)

对于正在加载寄存器的特定问题,U将其设为无符号值,但是编译器是否将n位字模式视为有符号值或无符号值,都会移动相同的位模式,假设没有任何大小扩展会传播MSB。可能重要的区别在于寄存器加载操作是否将基于有符号或无符号加载来设置任何处理器条件标志。作为总体指导,如果处理器支持将常量存储到配置寄存器或存储器地址,则加载外设寄存器不太可能设置处理器的NEG条件标志。加载连接到ALU的通用寄存器,该寄存器可以作为加法或减法之类的算术运算的目标,可能在加载时设置一个负标志,例如尾随的“分支(如果为负)”操作码将执行分支。您可能需要检查处理器的引用以确保。小型指令集处理器倾向于仅具有一个加载寄存器指令,而较大的指令集则更有可能具有未在处理器标志中设置NEG位的加载指令的无符号加载变体,但再次检查处理器的引用。如果您无权访问处理器的勘误表(boo-boo列表),并且需要特定的标志状态。只有当优化的编译器使用内联汇编指令和其他不常见的情况重新排列代码时,所有这些趋向于出现。检查生成的汇编代码,在需要时关闭模块的部分或全部编译器优化,等等。