【问题标题】:No compiler error when macros that weren't previously defined are used使用以前未定义的宏时没有编译器错误
【发布时间】:2015-09-23 11:31:45
【问题描述】:

我想问你为什么在下面的代码中编译器没有报错? 这是 flash.h 文件:

#ifndef _FLASH_H_
#define _FLASH_H_
#define BANK_A    0
#define BANK_B    1
#define BANK_C    3
#define FLASH_IS_BUSY         (FCTL3 & BUSY)//FCTL3 and BUSY are defined in msp430f5438a.
#endif

这是 main.c 文件:

#include "flash.h"
#include <msp430f5438a.h>
void main(void)
{
   while(1)
   {
      ;
   }
}

问题是我不明白编译器如何在这一行没有给出错误:

#define FLASH_IS_BUSY         (FCTL3 & BUSY)

由于没有办法(根据我的掌握)编译器知道 FCTL3 和 BUSY 是什么意思。这两个宏在 msp430f5438a.h 中定义如下:

#define FCTL3  (*((unsigned char*)0x0144u))
#define BUSY   0x01  

但 flash.h 包含在 msp430f5438a.h 之前编译器如何解析这些符号:FCTL3 和 BUSY?

【问题讨论】:

  • 宏在解析时间/之前被替换。所以理论上编译器看不到FCTL3和BUSY
  • 编译器不能警告所有(甚至大多数)可能的错误。阅读Rice's theorem和Halting problem

标签: c compiler-errors macros preprocessor-directive


【解决方案1】:

这里需要注意的是,#defines 在预处理阶段用作文本替换,它们与变量声明或定义不同。

在您的头文件中,您只定义了FLASH_IS_BUSY MACRO,但在您的代码中,您没有使用它。甚至,如果您使用过它,在 main() 之前,标头 #include &lt;msp430f5438a.h&gt; 就在那里,这使得 FCTL3 和 BUSY 的定义可用于您的代码(如果使用)。

TL;DR 头文件中的FLASH_IS_BUSY 宏定义 不需要已经定义FCTL3 和BUSY。例如,您可以使用 -D 选项和 gcc 提供这些 MACRO 值,它们根本不需要出现在代码中。

也就是说,main() 的推荐签名是int main(void)

【讨论】:

  • msp430 是一个微控制器,所以int main (void) 很可能是不正确和坏的。
  • @Lundin 嗯? int main(void) 是标准 C 所需的签名之一。微控制器没有命令行,所以 int main(void) 最有意义,另一种选择是 int main(int, char**)。
  • @Lundin 先生,为我辩护,我想提出两点。 1)问题没有用msp430或类似的标签,而只有C和2)有问题的case与它没有直接关系,所以标签也不是必需的,最后一行只是针对 C 标准的建议。就这样。希望我没有被误解。 :-)
  • 好的,有人愿意通过分享一些关于否决票的原因来帮助我吗?
  • @Potatoswatter 独立实现具有完全实现定义的签名,请参阅 5.1.2.1。
【解决方案2】:

仅限于您​​问题的特定主题(预处理器替换),如下:

#define FLASH_IS_BUSY (FCTL3 & BUSY)

您没有以任何方式使用FCTL3 和BUSY。您只是在指示编译器执行文本替换。它不需要知道任何关于它们的信息。

但是,当您像这样在代码中使用它时:

int flags = FLASH_IS_BUSY;

它将执行第一次替换:

int flags = (FCTL3 & BUSY);

替换循环将继续进行,直到有东西要替换(或达到硬阈值)。如果您忘记包含msp430f5438a.h,那么上面的代码将不会简单地编译(因为FCTL3 和BUSY 是未知的),但是如果您包含正确的头文件(无论顺序如何:msp430f5438a.h first 或@987654335 @ first)它们将被预处理器替换为实际值,您将拥有正确的代码:

int flags = ((*((unsigned char*)0x0144u)) & 0x01);

现在预处理器已完成,编译器(启用优化时)将(可能!!!)直接将 flags 替换为文字值。

注意事项:

  • 您的main() 签名是非标准的。它应该(对于托管环境)是int main(void) 或int main(int argc, char* argv[])。另见What is the proper declaration of main?。请注意,如果您的编译器允许void main(void) 签名,则它不是错误,而是只是 非标准。然而,Lunding 强调,在独立环境中main() 函数,根据 C 标准,签名是完全由实现定义的。请参阅他的启发性帖子:Why is the type of the main function in C and C++ left to the user to define?
  • 我知道while(1) 只是说明性的(它不是您的真实 代码),但它可能是早期C 规范中的UB。我不会在这里重复这个(长)讨论然后请参考Are compilers allowed to eliminate infinite loops?以进行进一步的研究。
  • u 后缀不是必需的,在 C 中整数文字总是非负数,然后 u 只是多余的。但是,某些特定指南可能要求(例如在关键系统中,请参阅MISRA),以明确(向读者)表明常量应为非负整数。李>
  • 您的空循环可能会被简单地优化掉。编译器可以不为; 生成任何代码。 Various workarounds exists 如果你想让它便携。

【讨论】:

  • 能否请您详细说明一下 while(1) 问题(在 C99 之前的标准规范方面)?至于为什么会被认为是UB?
  • msp430 是一个微控制器,所以int main (void) 很可能是不正确和错误的。由于它是一个独立的系统,void main (void) 很好,并且 100% 符合标准。
  • @Lundin 我没有写它是错误或者它是一个错误,我写的是"...签名是非标准的。”。我强调非标准,几乎每个编译器都允许 void main(void) 但它是一个编译器扩展,而不是标准所说的。但是,如果您从微控制器中的 main() 返回会发生什么是绝对未定义的。跳回去?执行垃圾?悬挂?如果你有一个明确的返回值,你(至少)可以避免简单的错误(例如,从另一个函数复制和粘贴代码)并且你的程序正确。
  • @Lundin Hosted vs. freestanding 对main的签名没有影响。
  • @Potatoswatter 在嵌入式系统上,或者实际上在任何程序中,总是在调用 main() 之前执行代码。嵌入式系统的不同之处在于程序永远不会终止。因此,如果您从重置向量中调用 int main(),那么调用约定将指示您在堆栈上为无用的 int 分配空间,这只是在具有内存约束的系统上浪费了内存。
【解决方案3】:

你还没有使用FLASH_IS_BUSY,所以预处理器不会替换不正确的语法。

顺便说一句,在旧的 C 标准(和新的 C++ 标准)中编写 while(1); 时要小心,因为您的程序的行为是未定义的。

见Is while(1); undefined behavior in C?

【讨论】:

  • 先生,无限循环如何调用 UB? 无用部分我理解,但是UB是怎么回事?
  • 这是一个众所周知的技术。如果循环体中有 printf 或类似的东西,那么一切都会好起来的。
  • 对不起,先生,我还是没找到您。也许您可以指向一些链接,我可以在其中阅读更多相关信息(如果不是问太多的话)?提前致谢。 :-)
  • 你是对的。请参阅链接的问题。仅从 C11 开始,not C++11 仍未定义。
  • @Lundin 永远旋转而不是返回的函数不是 C 抽象机的“结果”,而且从来都不是。尝试先研究。这个网站非常适合研究,那里有一个链接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-23
  • 2010-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多