C 语言:#DEFINed 值弄乱了 8 位乘法。 为什么?

发布于 2024-07-18 05:21:11 字数 669 浏览 9 评论 0原文

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

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

I have the following C code:

#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);

If I compile this (using an msp430 platform compiler, for an small embedded OS called contiki) the result is 0 while I expected 191.
(uint8_t is typedef'ed as an unsigned char)

If I change it to:

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

it works out correctly and prints 191.

Compiling a simple version of this 'normally' using gcc on an Ubuntu box prints the correct value in both cases.

I am not exactly sure why this is. I could circumvent it by assigning the DEFINEd value to a variable beforehand, but I'd rather not do that.

Does anybody know why this is? Perhaps with a link to some more information about this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

因为看清所以看轻 2024-07-25 05:21:11

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

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

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

The short answer: you compiler is buggy. (There is no problem with overflow, as others suggested.)

In both cases, the arithmetic is done in int, which is guaranteed to be at least 16 bits long. In the former snippet it's because 255 is an int, in the latter it's because of integral promotion.

As you noted, gcc handles this correctly.

天赋异禀 2024-07-25 05:21:11

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

 #define PRR_SCALE ((uint8_t) 255)

255 is being processed as an integer literal and causes the entire expression to be int based rather than unsigned char based. The second case forces the type to be correct. Try changing your #define as follows:

 #define PRR_SCALE ((uint8_t) 255)
A君 2024-07-25 05:21:11

如果有问题的编译器是 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 邮件列表中已经开始了关于此主题的讨论。

If the compiler in question is the mspgcc, it should put out an assembler listing of the compiled program together with the binary/hex file. Other compilers may require additional compiler flags to do so. Or maybe even a separate disassembler run on the binary.

This is the place where to look for an explanation.
Due to compiler optimizations, the actual code presented to the processor might have not much similarity to the original C code (but normally does the same job).

Stepping through the few assembler instructions representing the faulty code should reveal the cause of the problem.

My guess is that the compiler somehow optimizes the whole calculation sice the defined constant is a known part at compile time.
255*x could be optimized to x<<8-x (which is faster and smaller)
Maybe something is going wrong with the optimized assembler code.

I took the time to compile both versions on my system. With active optimization, the mspgcc produces the following code:

#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

As we can see, the compiler directly calculates the result of 255*3 to -3 (0xfffd). And here is the problem. Somehow the 255 gets interpreted as -1 signed 8-bit instead of 255 unsigned 16 bit. Or it is parsed to 8 bit first and then sign-extended to 16 bit. or whatever.

A discussion on this topic has been started at the mspgcc mailing list already.

-小熊_ 2024-07-25 05:21:11

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

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

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

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

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

I'm not sure why the define doesn't work, but you might be running into rollovers with the uint8_t variables. 255 is the max value for uint8_t (2^8 - 1), so if you multiply that by 3, you're bound to run into some subtle rollover problems.

The compiler might be optimizing your code, and pre-calculating the result of your math expression and shoving the result in prr (since it fits, even though the intermediate value doesn't fit).

Check what happens if you break up your expression like this (this will not behave like what you want):

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

You may need to just use a larger datatype.

国际总奸 2024-07-25 05:21:11

我认为在情况 1 中的一个区别是,

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

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

它可能根本没有意义。

One difference I can think in case-1 is,

The PRR_SCALE literal value may go into ROM or code area. And there may be some difference in the MUL opecode for say,

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

It may not make sense at all.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文