6

我有以下 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 被 typedef 为无符号字符)

如果我将其更改为:

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 值分配给变量来规避它,但我宁愿不这样做。

有人知道这是为什么吗?也许有一个链接到有关此的更多信息?

4

5 回答 5

10

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

在这两种情况下,算术都是在 中完成的int,保证至少有 16 位长。在前一个片段中是因为255是一个int,在后者中是因为积分提升

正如您所指出的,gcc 可以正确处理此问题。

于 2009-04-26T22:03:08.633 回答
2

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

 #define PRR_SCALE ((uint8_t) 255)
于 2009-04-26T21:33:15.320 回答
2

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

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

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

我的猜测是编译器以某种方式优化了整个计算,因为定义的常量在编译时是已知的部分。255*x 可以优化为 x<<8-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 邮件列表中开始了有关此主题的讨论。

于 2009-04-29T11:47:36.653 回答
1

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

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

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

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

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

于 2009-04-26T21:48:16.780 回答
0

在 case-1 中我能想到的一个区别是,

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

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

这可能根本没有意义。

于 2009-04-27T07:15:46.353 回答