外部变量仅在 header 意外工作,为什么?
Extern variable only in header unexpectedly working, why?
我目前正在更新 Arduino 的 C++ 库(特别是使用 avr-gcc 编译的 8 位 AVR 处理器)。
通常,默认 Arduino 库的作者喜欢在 header 中包含 class 的外部变量,该变量也在 class .cpp 文件中定义。我假设这基本上是为新手准备好一切,如 built-in objects.
我的情况是:我更新的库不再需要.cpp 文件,我已将其从库中删除。直到我进行最后一次错误检查时我才意识到,尽管没有为 .cpp 文件中的 extern
变量提供定义,但没有产生链接器错误。
这很简单(header 文件):
struct Foo{
void method() {}
};
extern Foo foo;
包含此代码并在一个或多个源文件中使用它不会导致任何链接器错误。我已经在 Arduino 使用的两个 GCC 版本(4.3.7、4.8.1)和 C++11 enabled/disabled.
中进行了尝试
在我试图导致错误的过程中,我发现只有在执行诸如获取 object 的地址或修改我添加的虚拟变量的内容时才有可能。
发现这一点后,我发现它很重要:
- class 仅对 return 其他 object 起作用,就像运算符 return 引用自身甚至副本一样。
- 它只修改外部 objects(在代码中有效地
volatile uint8_t
引用的寄存器)和 returns 其他 classes 的临时文件。
- 此 header 中的所有 class 函数都非常基础,以至于它们的成本低于或等于函数调用的成本,因此它们(在我的测试中)完全 in-lined 进入调用者。一个典型的语句可能会在调用链中创建许多临时 object,但是编译器会看穿这些并直接输出高效的代码修改寄存器,而不是一组嵌套的函数调用。
我还记得在 n3797 7.1.1 - 8 中读到 extern
可以用于不完整的类型,但是 class已完全定义,而声明未定义(这可能无关紧要)。
我相信这可能是优化的结果。我已经看到获取地址对 objects 的影响,否则将被视为常量并且在不使用 RAM 的情况下进行编译。通过向编译器无法保证状态的 object 添加任何间接层将导致这种 RAM 消耗行为。
所以,也许我只是通过简单的提问来回答我的问题,但我仍然在做假设,这让我很困扰。经过一段时间 hobby-coding C++,我的 do-not 的 列表中唯一的东西就是 做出假设 。
真的,我想知道的是:
- 关于我的工作解决方案,它是记录无法获取 class 地址(导致间接寻址)的简单案例吗?
- 这是否只是一种边缘情况行为,是由于优化消除了链接某些内容的需要而导致的?
- Or 是简单明了的未定义行为。因为在 GCC 中可能有一个错误并且允许代码如果降低或禁用优化可能会失败?
或者你们中的一个人可能幸运地拥有一个解码器环,可以在标准中找到合适的段落来概述细节。
这是我的第一个问题,如果您想了解某些细节,请告诉我,如果需要,我也可以提供 GitHub 代码链接。
编辑: 由于库需要与现有代码兼容,我需要保持使用点语法的能力,否则我只会有一个 class 的静态函数。
为了暂时消除假设,我看到了两个选择:
- 为变量声明添加一个 .cpp。
- 使用 header 中的定义,例如
#define foo (Foo())
允许通过临时句点语法。
我更喜欢使用 define 的方法,社区怎么看?
干杯。
声明某些东西 extern
只是通知汇编器和链接器,无论何时使用 label/symbol,它都应该引用符号 table 中的条目,而不是本地分配的符号.
链接器的作用是尽可能用对地址 space 的实际引用替换符号 table 条目。
如果您在 C 文件中根本不使用该符号,它不会出现在汇编代码中,因此当您的模块与其他模块链接时不会导致任何链接器错误,因为没有未定义的引用。
这要么是优化导致的边缘情况行为,要么您从未在代码中使用 foo
变量。我不是 100% 确定它在形式上不是未定义的行为,但我很确定从实际的角度来看它不是未定义的。
extern
变量以这样的方式实现,用它们编译的代码会产生所谓的重定位 - 应该放置变量地址的空位 - 然后由链接器填充。显然 foo
从未以需要获取其地址的方式在您的代码中使用,因此链接器甚至不会尝试查找该符号。如果您关闭优化 (-O0),您可能会收到链接器错误。
更新: 如果你想保留 "dot notation" 但要解决 undefined extern 的问题,你可以将 extern
替换为 static
(在头文件中),为每个 TU 创建单独的 "instance" 变量。由于这个变量无论如何都会被优化掉,所以这根本不会改变真正的代码,但也适用于未优化的构建。
我目前正在更新 Arduino 的 C++ 库(特别是使用 avr-gcc 编译的 8 位 AVR 处理器)。
通常,默认 Arduino 库的作者喜欢在 header 中包含 class 的外部变量,该变量也在 class .cpp 文件中定义。我假设这基本上是为新手准备好一切,如 built-in objects.
我的情况是:我更新的库不再需要.cpp 文件,我已将其从库中删除。直到我进行最后一次错误检查时我才意识到,尽管没有为 .cpp 文件中的 extern
变量提供定义,但没有产生链接器错误。
这很简单(header 文件):
struct Foo{
void method() {}
};
extern Foo foo;
包含此代码并在一个或多个源文件中使用它不会导致任何链接器错误。我已经在 Arduino 使用的两个 GCC 版本(4.3.7、4.8.1)和 C++11 enabled/disabled.
中进行了尝试在我试图导致错误的过程中,我发现只有在执行诸如获取 object 的地址或修改我添加的虚拟变量的内容时才有可能。
发现这一点后,我发现它很重要:
- class 仅对 return 其他 object 起作用,就像运算符 return 引用自身甚至副本一样。
- 它只修改外部 objects(在代码中有效地
volatile uint8_t
引用的寄存器)和 returns 其他 classes 的临时文件。 - 此 header 中的所有 class 函数都非常基础,以至于它们的成本低于或等于函数调用的成本,因此它们(在我的测试中)完全 in-lined 进入调用者。一个典型的语句可能会在调用链中创建许多临时 object,但是编译器会看穿这些并直接输出高效的代码修改寄存器,而不是一组嵌套的函数调用。
我还记得在 n3797 7.1.1 - 8 中读到 extern
可以用于不完整的类型,但是 class已完全定义,而声明未定义(这可能无关紧要)。
我相信这可能是优化的结果。我已经看到获取地址对 objects 的影响,否则将被视为常量并且在不使用 RAM 的情况下进行编译。通过向编译器无法保证状态的 object 添加任何间接层将导致这种 RAM 消耗行为。
所以,也许我只是通过简单的提问来回答我的问题,但我仍然在做假设,这让我很困扰。经过一段时间 hobby-coding C++,我的 do-not 的 列表中唯一的东西就是 做出假设 。
真的,我想知道的是:
- 关于我的工作解决方案,它是记录无法获取 class 地址(导致间接寻址)的简单案例吗?
- 这是否只是一种边缘情况行为,是由于优化消除了链接某些内容的需要而导致的?
- Or 是简单明了的未定义行为。因为在 GCC 中可能有一个错误并且允许代码如果降低或禁用优化可能会失败?
或者你们中的一个人可能幸运地拥有一个解码器环,可以在标准中找到合适的段落来概述细节。
这是我的第一个问题,如果您想了解某些细节,请告诉我,如果需要,我也可以提供 GitHub 代码链接。
编辑: 由于库需要与现有代码兼容,我需要保持使用点语法的能力,否则我只会有一个 class 的静态函数。
为了暂时消除假设,我看到了两个选择:
- 为变量声明添加一个 .cpp。
- 使用 header 中的定义,例如
#define foo (Foo())
允许通过临时句点语法。
我更喜欢使用 define 的方法,社区怎么看?
干杯。
声明某些东西 extern
只是通知汇编器和链接器,无论何时使用 label/symbol,它都应该引用符号 table 中的条目,而不是本地分配的符号.
链接器的作用是尽可能用对地址 space 的实际引用替换符号 table 条目。
如果您在 C 文件中根本不使用该符号,它不会出现在汇编代码中,因此当您的模块与其他模块链接时不会导致任何链接器错误,因为没有未定义的引用。
这要么是优化导致的边缘情况行为,要么您从未在代码中使用 foo
变量。我不是 100% 确定它在形式上不是未定义的行为,但我很确定从实际的角度来看它不是未定义的。
extern
变量以这样的方式实现,用它们编译的代码会产生所谓的重定位 - 应该放置变量地址的空位 - 然后由链接器填充。显然 foo
从未以需要获取其地址的方式在您的代码中使用,因此链接器甚至不会尝试查找该符号。如果您关闭优化 (-O0),您可能会收到链接器错误。
更新: 如果你想保留 "dot notation" 但要解决 undefined extern 的问题,你可以将 extern
替换为 static
(在头文件中),为每个 TU 创建单独的 "instance" 变量。由于这个变量无论如何都会被优化掉,所以这根本不会改变真正的代码,但也适用于未优化的构建。