最近因为一门实训课程的缘故接触了 Arduino 开发。由于有一定的 C++ 基础,所以还是很快上手了 Arduino 提供的开发环境,简单说来就是一个 C++ 工程,所谓的“Sketch”(.ino文件)其实就是 C++ 源代码。然而其中有一点令我很困惑,并最终导致了问题的产生: Arduino IDE 是如何处理源代码中的头文件引用并最终链接整个程序的?

这个问题听上去根本不是问题:不就是和一般的 C++ 一样使用 #include 预编译指令么?顶多最终链接的时候 IDE 做一些自动处理,把用到的模块都链接起来。但如果你用过 Arduino 的 library ,你就会发现它的使用方法不符合 C++ 的规则:库是存放在 library 目录下的一个个子文件夹中的,而使用的时候却是直接 #include 某个头文件,没有指明库文件夹的名字( C++ 的头文件引用是不会递归搜索的)。

起初我还不是很重视这个问题,直到后来开始仿写 Arduino 库或者自己写模块(后缀名为 .cpp 和 .h ,放在项目目录下)——我发现在这些文件里,我无法像在 Sketch 中那样方便地直接引用某个库的头文件!更奇怪的是,当我以 #include < ...> 这样的形式引用文件时(库头文件就要求这样引用),即使文件不存在也不会在这一行报错。我开始怀疑 Arduino IDE 对我的源代码做了一些处理。

接下来,我发现一个项目中可以建立多个 Sketch ,而且不需要写任何 #include 就能“合并”到一起,并且这个“合并”的行为和正常的多文件 .cpp 有些不一致——尤其是即使我没有指定这些文件的使用顺序,也没有写函数原型,所有的函数都能正常使用。此外, #define#ifdef 的表现也十分 weird 。

到这里,我基本上确定 Arduino IDE 在编译、链接之前对我的代码做了预处理。上网搜了一番,就找到了这个东西: Arduino Build Process

其实接下来都是废话了,上面这个链接清清楚楚地说明了 IDE 对代码所做的所有预处理!到这时我才明白,之前许多折腾都是在白费时间,要是能早些想到去读读官方文档,不要想当然就好了。不过我还是简单写一点笔记,权当学习心得了:

Sketch 文件预处理

对于 Sketch 文件(后缀名是 .ino ,在 IDE 中显示为无后缀名), Arduino IDE 会进行一系列预处理,最终将它们捆绑成一个 main.cxx :

  1. 在一开头加入 #include "Arduino.h"
  2. 遍历所有 .ino 文件中定义的函数,自动生成函数原型,将其加到文件的开头(在所有注释和预编译指令之后,所有语句之前),然后将所有 .ino 文件拼接起来。

以上第二步中的坑很多,包括由于函数原型被插在了所有语句之前,包括 typedef 语句,所以若函数参数和返回值里用到了自定义类型,就会失败;以及这个自动处理无法正确处理类及 namespace 等等……

普通的 C/C++ 文件不会经过以上的预处理!

编译及链接

首先, Arduino 的基本库文件、 avr 头文件、 C 标准库文件会被加入引用目录列表。

Arduino IDE 会扫描 main.cxx (即所有的 .ino 文件)中的 #include < ...> ,如果无法在当前引用目录里找到的话,就会去 library 目录下查找所有子目录(只找一级),如果找到,就将其加入引用目录列表之中。这就是那个库引用黑魔法的原理。知道了原理以后你就可以意识到:库文件夹的名字不重要;如果有两个库中有名字相同的头文件,会出现无法预期的行为。

而对于普通的 C/C++ 文件,则会单独编译成一个静态库,最后和 main.cxx 以及各个 library 的编译结果链接起来。

一些影响和限制

一、无法在 Arduino 库中直接引用其他库。比较 tricky 的办法是 #include "../<library name>/some_library.h" 。但是这是有问题的:无法保证所有 library 都处在同一个目录下,比如项目 library 和全局 library 。 Stackoverflow 上见到的解决方案包括要求用户在使用这个库的时候于 #include 这个库之前先 #include 另一个库。

二、无法在普通 C/C++ 文件中方便地引用其他库。因此尽量把需要操作硬件、使用库的东西放进 .ino 里头, C/C++ 里头可以放一些硬件无关的数据处理代码。

三、留意多 Sketch 文件的处理方式,以免发生错误。

最后,我从这次的经历中学到的最重要的东西其实不是这个“知识”,而是一个经验:当出现奇怪的问题时,不妨先读读官方文档,不要瞎猜、想当然,尤其是针对自己自认为有所了解却又不是很熟悉的领域。