在 C 中,为什么这个具有递增整数的“main”函数永远不会因溢出而崩溃?

B1a*_*ge1 2 c embedded microcontroller stm32

main在C语言中,像stm32这样的嵌入式系统最基本的函数是:

int main(void) {
    int i = 0;

    while(1) {
        i++;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,由于i是一个整数,它不会溢出并因此在某些时候产生问题吗?

while(1) i++另外,在等待特定中断时使用相同的策略(使用 )循环是否安全?

编辑:感谢您的所有回答。我很遗憾不知道溢出处理程序与 C 和 C++ 有很大不同。现在我明白了 !

Gab*_*les 7

以下内容适用于 C 和 C++。以下所有代码均可编译为 C 和 C++。

\n
\n

为什么这个main带有递增整数的函数永远不会溢出?

\n
\n

它确实溢出了。然而,由于整数是有符号的,因此它是未定义的行为并且是一个错误。根据正在编译代码的编译器和硬件体系结构,编译器可能会以预期的方式处理有符号整数溢出和下溢,例如:在溢出时回绕到最小值,但这不是保证,每个都不允许语言标准,这不是一个好主意。

\n

请参阅此问题中的引号:为什么无符号整数溢出定义了行为,但有符号整数溢出却没有?(强调):

\n
\n

C 和 C++ 标准都明确定义了无符号整数溢出。例如,[C99 标准][http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf] ( \xc2\xa76.2.5/9) 规定

\n
\n

涉及无符号操作数的计算永远不会超过\xef\xac\x82ow, \n因为无法由结果无符号整数类型表示的结果\n会以比可表示的最大值大 1 的数字进行归约。结果类型。

\n
\n

然而,这两个标准都规定有符号整数溢出是未定义的行为。再次,来自 C99 标准 ( \xc2\xa73.4.3/1)

\n
\n

unde\xef\xac\x81ned 行为的一个示例是整数 over\xef\xac\x82ow的行为

\n
\n
\n

下一个问题:

\n
\n

while(1) i++另外,在等待特定中断时使用相同的策略(使用 )循环是否安全?

\n
\n

在微控制器中使用以下内容就可以了,是的:

\n
int main(void) \n{\n    while (1) \n    {\n        // loop continually as fast as the processor can, while waiting for \n        // interrupts; ISR (interrupt service routine) function definitions not\n        // shown \n    }\n\n    // On a microcontroller, this code must never be reached!\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然而,在任何具有操作系统的系统上,您应该在 while 循环中休眠,即使只是一毫秒,以便让调度程序有机会运行其他进程。然而,在裸机(意思是:没有操作系统)微控制器上,无限 while 循环尽可能快地运行是正常的。

\n
\n

您不得依赖未定义的行为有符号整数上溢和下溢。相反,依赖于明确定义的无符号整数上溢和下溢行为。

\n

这完全没问题:

\n
#include <stdint.h>  // for uint16_t, etc.\n\nint main(void) \n{\n    uint16_t counter = 0;\n    while (1) \n    {\n        counter++; // increment unsigned counter; overflow is fine\n\n        // use the `counter` variable somewhere or else it may be optimized out\n        // completely by the compiler\n\n        // loop continually as fast as the processor can, while waiting for \n        // interrupts; ISR (interrupt service routine) function definitions not\n        // shown \n    }\n\n    // On a microcontroller, this code must never be reached!\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

对于无符号整数,这样的事情也完全没问题。请注意,这uint8_t是一个无符号 8 位整数,值范围为 0 到 255:

\n
uint8_t u1 = 0 - 1;  // underflows to 255 at compile-time\n\nuint8_t u2 = 0; \nu2 - 1;  // underflows to 255; can be at run-time\n\nuint8_t u2 = 255;\nu2 + 1; // overflows to 0; can be at run-time\n\nuint8_t u3 = 255;\nu3 + 2; // overflows to 1; can be at run-time\n\nuint8_t timestamp_end = 128;\nuint8_t timestamp_beginning = 129;\n// The result is 255\nuint8_t time_difference = timestamp_end - timestamp_beginning; \n\ntimestamp_end = 100;\ntimestamp_beginning = 200;\n// The result is 156\ntime_difference = timestamp_end - timestamp_beginning;\n
Run Code Online (Sandbox Code Playgroud)\n

while有关微控制器中无限循环的更多信息

\n

作为示例,下面是main()Arduino 中 AVR 微控制器内核的 C++ 函数(例如:ATmega328:Arduino Uno、Nano 等)。

\n

注意无限for (;;)循环。使用与使用orfor (;;)完全相同。在里面,它调用用户定义的函数,只要定义了该函数就调用(请参见下面的注释 1),然后重复,与处理器运行的速度一样快!您编写的其他所有内容都必须通过主循环外部的 ISR 中处理的中断来处理,或者通过主循环内部的协作多任务处理(请参阅我在此处的答案中执行此操作的一些示例代码),这确实非常有效。while (1)while (true)loop()serialEventRun()

\n

注1:在gcc编译器中,__attribute__((weak))从未定义的函数等于0,这将导致if (serialEventRun)错误。请参阅我的eRCaGuy_hello_world存储库中的测试代码:cpp/check_addr_of_weak_undefined_funcs.cpp。示例输出位于该文件的最底部。

\n

serialEventRun()函数调用可选的、自定义的、用户定义的serialEvent()函数(如果已定义)。如果用户没有定义它,它就是一个声明为 的弱空函数void serialEvent() __attribute__((weak));。请参阅我对此的回答:Arduino“SerialEvent”示例代码不适用于我的 Arduino Nano。我无法接收串行数据。为什么?

\n

还要注意USBDevice.attach();来电。我还没有研究过它,但我怀疑将回调附加到 ISR(中断服务例程函数),以便每当(显然)发生 USB 相关中断时都会调用回调函数。

\n

另请注意,在进入无限循环之前,用户的setup()函数仅被调用一次,for (;;)而用户的loop()函数在无限for (;;)循环内被重复调用:

\n

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/main.cpp

\n
#include <Arduino.h>\n\n// Declared weak in Arduino.h to allow user redefinitions.\nint atexit(void (* /*func*/ )()) { return 0; }\n\n// Weak empty variant initialization function.\n// May be redefined by variant files.\nvoid initVariant() __attribute__((weak));\nvoid initVariant() { }\n\nvoid setupUSB() __attribute__((weak));\nvoid setupUSB() { }\n\nint main(void)\n{\n    init();\n\n    initVariant();\n\n#if defined(USBCON)\n    USBDevice.attach();\n#endif\n    \n    setup();\n    \n    for (;;) {\n        loop();\n        if (serialEventRun) serialEventRun();\n    }\n        \n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

迂腐的、律师般的语言笔记

\n

来自@Eric Postpischil 的评论

\n
\n

C++ 2020 草案 n4849 6.8.1 在注释中说,

\n
\n

无符号算术不会溢出。有符号算术的溢出会产生未定义的行为 (7.1)。

\n
\n

按照标准的规定,有符号整数算术会溢出,因为指定的结果(例如加法的总和)无法用结果类型表示,而无符号算术永远不会溢出,因为操作被指定为换行,因此指定的结果是总是有代表性的。

\n
\n

换句话说,从技术上讲,“溢出”一词意味着“无法保存指定的结果”,因此只有有符号整数才能执行此操作,但无符号整数不会执行此操作,因为它们被指定为环绕,因此它们的结果始终是可表示的。

\n

这与我们(几乎所有人)使用这些词的方式相反。我们使用“溢出”来表示“环绕”,因此无符号整数可以正确溢出,但有符号整数则不会。再说一遍,从技术上讲,我们用我们的常用白话(包括我自己)来看待这些定义,甚至在我上面的回答中也是如此。

\n

但是,我们不是这里的语言律师,所以我将继续说无符号整数具有“明确定义”的下溢和上溢,而有符号整数具有“未定义的行为”下溢和上溢。

\n

也可以看看

\n
    \n
  1. 原来我以前回答过这样的问题。请参阅此处我的答案:Adding int type to uint64_t c++: Undefinebehavioursigned- integeroverflow/underflow, and well-defededbehaviourunsigned- integeroverflow/underflow, in C and C++
  2. \n
\n\n