【问题标题】:C language: #DEFINEd value messes up 8-bit multiplication. Why?C 语言:#DEFINed 值弄乱了 8 位乘法。为什么?
【发布时间】:2009-04-26 21:27:05
【问题描述】:

我有以下 C 代码:

#define PRR_SCALE 255
...
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
printf("prr: %u\n", prr);

如果我编译它(使用 msp430 平台编译器,对于名为 contiki 的小型嵌入式操作系统),结果为 0,而我预期为 191。 (uint8_t 被定义为无符号字符)

如果我将其更改为:

uint8_t a = 3;
uint8_t b = 4;
uint8_t c = 255;
uint8_t prr;
prr = (c * a) / b;
printf("prr: %u\n", prr);

它运行正常并打印 191。

在 Ubuntu 机器上使用 gcc 编译这个“通常”的简单版本会在两种情况下打印正确的值。

我不确定这是为什么。我可以通过预先将 DEFINEd 值分配给变量来规避它,但我宁愿不这样做。

有人知道这是为什么吗?也许带有指向更多相关信息的链接?

【问题讨论】:

  • 我当然希望两者都打印 191。在第二种情况下,第一个 c 和 a 被独立提升为 int,因此它们的乘法不会溢出。在第一种情况下也会发生同样的情况(尽管 PRR_SCALE 已经是 int - 但这也不会改变 a 到 int 的提升)。你盒子上的 gcc 表现得很好。
  • 检查是否包含标头 stdio.h。我知道 msp430 的一个编译器确实允许发生隐式函数声明:如果是这种情况,对 printf 的调用将导致未定义的行为,因此将解释“0”结果。只是我的两分钱。不要认为这值得回答:)
  • 只是出于好奇:如果将 PRR_SCALE 设置为 255U 会发生什么?
  • @Jochen:打印结果是255!?我不知道为什么(再次)......
  • msp430 的 mul 代码使用乘法仿真代码(无硬件支持)iirc。所以很可能存在一些我怀疑的错误(尽管这是一个相当 C 前端的问题)。作为修复,尝试 (+PRR_SCALE * +a) / +b;并添加一条评论,“+”将手动提升它们。也许这会有所帮助?

标签: c embedded overflow multiplication msp430


【解决方案1】:

简短的回答:你的编译器有问题。 (正如其他人所建议的那样,溢出没有问题。)

在这两种情况下,算法都是在int 中完成的,它保证至少有 16 位长。前者是因为255int,后者是因为integral promotion

正如您所说,gcc 可以正确处理这个问题。

【讨论】:

  • “算术是在 int 中完成的,保证至少 16 位长”——你能提供一个来源吗?我怀疑它的有效性......
  • “算术在 int 中完成”:积分提升的结果。 “[int] 保证至少 16 位长”:间接保证,INT_MAX 必须至少为 32767,INT_MIN 至多 -32767(参见标准中有关 limits.h 的部分)。
  • +1,这就是我调试了一下之后的预期。您能否提供一个链接来指定如何将值转换回来?还是只是模运算?
  • +1 还有什么 :) 顺便说一下,在二元运算符面对两个操作数并且想要将它们转换为单一类型的情况下,完成的转换称为“通常的算术转换” ”。在其中完成的转换是您所指的整体促销活动。确实,这两种情况都会发生这种情况。
  • JaredPar,如果转换的源类型或目标类型被签名,则仅当值可以在目标类型中表示时才定义行为。对于无符号类型,使用模运算。
【解决方案2】:

255 被处理为整数文字,并导致整个表达式基于 int 而不是基于 unsigned char。第二种情况强制类型正确。尝试按如下方式更改您的#define:

 #define PRR_SCALE ((uint8_t) 255)

【讨论】:

  • 不行,我已经尝试过了(嗯,我直接在计算行中进行了转换。我按照你的建议重试了。没有区别)。
  • 我都是代码示例,算术是在 int 中完成的。这是由于整数提升,请参阅open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf,第 6.3.1.1 节,第 2 段。
【解决方案3】:

如果有问题的编译器是 mspgcc,它应该将已编译程序的汇编程序列表与二进制/十六进制文件一起列出。其他编译器可能需要额外的编译器标志来执行此操作。或者甚至可以在二进制文件上运行一个单独的反汇编程序。

这是寻找解释的地方。 由于编译器的优化,实际呈现给处理器的代码可能与原始 C 代码没有太大的相似性(但通常做相同的工作)。

单步执行代表错误代码的几条汇编指令应该可以揭示问题的原因。

我的猜测是编译器以某种方式优化了整个计算,因为定义的常量在编译时是已知的部分。 255*x 可以优化为 x

我花时间在我的系统上编译这两个版本。通过主动优化,mspgcc 生成以下代码:

#define PRR_SCALE 255
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
    40ce:   3c 40 fd ff     mov #-3,    r12 ;#0xfffd
    40d2:   2a 42           mov #4, r10 ;r2 As==10
    40d4:   b0 12 fa 6f     call    __divmodhi4 ;#0x6ffa
    40d8:   0f 4c           mov r12,    r15 ;
printf("prr: %u\n", prr);
    40da:   7f f3           and.b   #-1,    r15 ;r3 As==11
    40dc:   0f 12           push    r15     ;
    40de:   30 12 c0 40     push    #16576      ;#0x40c0
    40e2:   b0 12 9c 67     call    printf      ;#0x679c
    40e6:   21 52           add #4, r1  ;r2 As==10

我们可以看到,编译器直接将255*3的结果计算为-3(0xfffd)。这就是问题所在。不知何故,255 被解释为 -1 有符号 8 位而不是 255 无符号 16 位。或者先解析为 8 位,然后符号扩展为 16 位。或其他。

已经在 mspgcc 邮件列表中开始了有关此主题的讨论。

【讨论】:

    【解决方案4】:

    我不确定为什么定义不起作用,但您可能会遇到 uint8_t 变量的翻转。 255 是uint8_t (2^8 - 1) 的最大值,因此如果将其乘以 3,则必然会遇到一些微妙的翻转问题。

    编译器可能正在优化您的代码,并预先计算您的数学表达式的结果并将结果推送到 prr 中(因为它适合,即使中间值不适合)。

    检查如果你这样分解你的表达式会发生什么(这不会像你想要的那样):

    prr = c * a; // rollover!
    prr = prr / b;
    

    您可能只需要使用更大的数据类型。

    【讨论】:

    • 技术上 c*a 不会溢出。 C 标准规定无符号整数类型不能发生溢出。
    • 酷,有道理。我想“翻转”会是一个更好的词。
    【解决方案5】:

    我认为 case-1 的一个区别是,

    PRR_SCALE 文字值可能会进入 ROM 或代码区域。并且 MUL 操作码可能存在一些差异,例如,

    case-1: [register], [rom]
    case -2: [register], [register]
    

    这可能根本没有意义。

    【讨论】:

      猜你喜欢
      • 2010-09-30
      • 1970-01-01
      • 2014-02-18
      • 2021-12-29
      • 2013-04-05
      • 1970-01-01
      • 1970-01-01
      • 2017-05-26
      • 1970-01-01
      相关资源
      最近更新 更多