我目前正在为Arduino更新C ++库(特别是使用avr-gcc编译的8位AVR处理器)。
通常,默认Arduino库的作者喜欢在头部内包含类的外部变量,该变量也在类.cpp文件中定义。我认为这基本上是为了让新手作为内置对象准备就绪。
我的方案是:我更新的库不再需要.cpp文件,我已将其从库中删除。直到我进行最后一次检查才发现我发现的错误,但是没有产生链接器错误,尽管事实上没有为.cpp文件中的extern
变量提供定义。
这很简单,我可以得到它(头文件):
struct Foo{
void method() {}
};
extern Foo foo;
包含此代码并在一个或多个源文件中使用它不会导致任何链接器错误。我在两个版本的GCC中尝试过它,Arduino使用(4.3.7,4.8.1)和启用/禁用C ++ 11。
在我试图导致错误的过程中,我发现只有在执行某些操作时才能获取对象的地址或修改我添加的虚拟变量的内容。
发现这一点后,我发现它很重要:
volatile uint8_t
引用的寄存器),并返回其他类的临时对象。我还记得在n3797 7.1.1 - 8 中阅读extern
可以用于不完整类型,但是该类是完全定义的,而声明不是(这可能是无关紧要的。)
我认为这可能是游戏优化的结果。我已经看到了获取地址对对象的影响,否则这些对象将被视为常量并且在没有使用RAM的情况下进行编译。通过向编译器无法保证状态的对象添加任何间接层,将导致此RAM消耗行为。
所以,也许我只是通过询问来回答我的问题,但是我仍然在做假设并且困扰我。经过相当长一段时间的爱好编码C ++,字面上我不做列表中的唯一内容是做出假设。
真的,我想知道的是:
或者你们中的一个人可能很幸运拥有一个解码器环,可以在标准中找到一个合适的段落,概述具体细节。
这是我的第一个问题,所以如果您想了解某些细节,请告诉我。如果需要,我还可以提供GitHub链接代码。
编辑:由于库需要与现有代码兼容,我需要保持使用点语法的能力,否则我只需要一类静态函数。
为了删除现在的假设,我看到两个选项:
#define foo (Foo())
这样的定义,允许通过临时的点语法。我更喜欢使用 define 的方法,社区的想法是什么?
干杯。
答案 0 :(得分:2)
声明某些内容extern
只是告知汇编器和链接器,无论何时使用该标签/符号,它都应该引用符号表中的条目,而不是本地分配的符号。
链接器的作用是尽可能用符号表条目替换地址空间的实际引用。
如果您在C文件中根本不使用该符号,它将不会显示在汇编代码中,因此当您的模块与其他模块链接时不会导致任何链接器错误,因为没有未定义的参考。
答案 1 :(得分:1)
这是由优化引起的边缘情况行为,或者您从未在代码中使用foo
变量。我并非100%确定它在形式上不是一种不确定的行为,但我确信从实际角度来看它并不是不确定的。
extern
变量以这种方式实现,用它们编译的代码产生所谓的重定位 - 应该放置变量addres的空位 - 然后由链接器填充。显然,foo
从未在您的代码中以需要获取其地址的方式使用,因此链接器甚至不会尝试找到该符号。如果关闭优化(-O0),可能会出现链接器错误。
更新:如果您想保留"点符号"但是使用未定义的extern删除问题,您可以将extern
替换为static
(在头文件中),创建单独的"实例"每个TU的变量。因为这个变量无论如何都会被优化,这根本不会改变真正的代码,但也可以用于未经优化的构建。