类成员的C++静态成员变量实例化了两次
C++ Static member variable of class member instantiated twice
我有一个模板类,它有两个静态成员变量,一个是int
,另一个是std::array<volatile uint_fast32_t, 8>
。当我用两个不同的类(它们本身就是模板)作为模板参数实例化模板时,对于其中一个实例化,一切都很完美,即两个变量都只有一个副本。然而,对于另一个,数组在符号表中重复出现,事实上,我的代码有一个错误,即当我在一个编译单元中设置数组中的值时,更改不会出现在另一个编译单位中。
这是针对嵌入式系统的,这就是使用静态模板进行编译时多态性这一奇怪习惯用法的原因。
在代码中:头声明类本身
//dacmux.h
namespace HAL {
template<typename dac_write_sequence_t,
unsigned int chans,
typename sample_t = uint_fast32_t>
struct dacmux {
private:
typedef std::array<volatile sample_t, chans> chans_t;
static chans_t channels;
static unsigned int nextchan;
...
};
//The static variables defined here,
//count on the compiler/linker to make sure
//there is exactly one definition
template<typename dac_write_sequence_t,
unsigned int chans,
typename sample_t>
typename dacmux<dac_write_sequence_t, chans, sample_t>::chans_t dacmux<dac_write_sequence_t, chans, sample_t>::channels{0};
template<typename dac_write_sequence_t, unsigned int chans, typename sample_t>
unsigned int dacmux<dac_write_sequence_t, chans, sample_t>::nextchan = 0;
template<typename dac_t, typename addr_t, typename en_t>
struct muxed_setter {
...
};
template<typename dac_t>
struct dac_setter {
...
};
}//namespace HAL
分发硬件定义的标头:
//Hardware_types.h
...
//Multiplexer for the internal DAC
typedef HAL::dacmux<HAL::muxed_setter<dac1, mux1_addr, mux1_en>, 8> mux1;
//Sequencer for writing the external DAC values
typedef HAL::dacmux<HAL::dac_setter<extdac1>, 8> extdac_sequencer;
...
头Hardware_types.h
包括在两个源文件main.cpp
、DebugConsole.cpp
中,这两个源都使用mux1
和extdac_sequencer
。
据我所知,基于这一个和许多其他答案,编译器应该注意每个静态成员变量在模板的每个实例化中都被实例化一次?
但是,当我在DebugConsole.cpp
中设置extdac_sequencer::channels
的值时,这些更改不会反映在main.cpp
中声明的中断处理程序中。mux1::channels
也是如此。事实上,符号表的摘录,由objdump -t
:从.elf中提取
20000280 l O .bss 00000004 _ZN3HAL6dacmuxINS_10dac_setterINS_6ti_dacINS_3SPIINS_5SPI_2EEEN5GPIOs5pin_tINS6_1BELj12EEENS_12_GLOBAL__N_110xx68_frameENSA_12command_xx68ENSA_12channel_xx68EEEEELj8EjE8nextchanE
...
20000254 l O .bss 00000020 _ZN3HAL6dacmuxINS_10dac_setterINS_6ti_dacINS_3SPIINS_5SPI_2EEEN5GPIOs5pin_tINS6_1BELj12EEENS_12_GLOBAL__N_110xx68_frameENSA_12command_xx68ENSA_12channel_xx68EEEEELj8EjE8channelsE
...
20000288 l O .bss 00000020 _ZN3HAL6dacmuxINS_10dac_setterINS_6ti_dacINS_3SPIINS_5SPI_2EEEN5GPIOs5pin_tINS6_1BELj12EEENS_12_GLOBAL__N_110xx68_frameENSA_12command_xx68ENSA_12channel_xx68EEEEELj8EjE8channelsE
...
20000234 w O .bss 00000020 _ZN3HAL6dacmuxINS_12muxed_setterIN4DACs11DAC_channelILj1EN5GPIOs5pin_tINS4_1AELj4EEEEENS4_12bit_stripe_tINS4_1CELj6ELj3EEENS5_ISA_Lj9EEEEELj8EjE8channelsE
...
2000027c w O .bss 00000004 _ZN3HAL6dacmuxINS_12muxed_setterIN4DACs11DAC_channelILj1EN5GPIOs5pin_tINS4_1AELj4EEEEENS4_12bit_stripe_tINS4_1CELj6ELj3EEENS5_ISA_Lj9EEEEELj8EjE8nextchanE
因此,nextchan
变量在每个实例化中出现一次,这是应该的,对于mux1
,channels
也是如此。但是,对于extdac_sequencer
,channels
变量是重复的,我认为这解释了错误。
是我做错了什么,还是这是编译器或链接器错误?
编译器:GCC arm none eabi 5.2.1 20151202
链接器:arm none eabi ld 2.25.90.20151217
链接器选项:-T mem.ld -T libs.ld -T sections.ld -nostartfiles -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"Synth1Firmware.map" -Xlinker --cref --specs=nano.specs
更新:
我已经缩小了发生这种情况的条件:
如果dacmux
的第一个模板参数本身不是模板,则一切正常,即没有重复符号:
struct extdac1_setter {
template<typename sample_t>
inline static void update(sample_t val, unsigned int addr) {
extdac1::write_and_update(val, addr);
}
};
//Multiplexer for external DAC, works
typedef HAL::dacmux<extdac1_setter, 8> extdac_sequencer;
然而,如果模板参数本身是模板化的,我会遇到重复符号的问题:
template<typename dac_t>
struct dac_setter {
template<typename sample_t>
inline static void update(sample_t val, unsigned int addr) {
dac_t::write_and_update(val, addr);
}
};
//Multiplexer for external DAC, this produces a duplicate symbol
typedef HAL::dacmux<dac_setter<extdac1>, 8> extdac_sequencer;
在这里,extdac1本身也是一个模板:
typedef HAL::DAC8568<dacspi, typename dacspi::nss> extdac1;
而dacspi
是一个模板,依此类推。此外,在工作的情况下,随着其他实例化,虽然dac_write_sequence_t
是一个模版,但它不再是模版中的模版。所以我开始认为这是模板递归深度的问题,即ld
看起来不够深。
另一个有趣的观察结果是:在与具有重复符号完全相同的条件下,Eclipse语法高亮显示在声明extdac_sequencer
的行上显示"无效模板参数",尽管实际的编译步骤已经完成。
原来这是我的愚蠢:我在定义模板HAL::DAC8568的头中使用了一个未命名的命名空间,该模板被定义为
template<typename spi_t, typename nss_t> using DAC8568 = ti_dac<spi_t, nss_t,
xx68_frame,
command_xx68,
channel_xx68>;
这里, 不同类型将未命名的命名空间更改为 链接器输出中损坏的名称似乎完全相同,这一事实仍然让我有些困惑。怎么可能呢? 无论如何,我们从中了解到以下内容(其中一些我们已经知道):xx68_frame
、command_xx68
和channel_xx68
都是在未命名的名称空间中定义的(当然,在标头中这样做是错误的)。这当然意味着,当从不同的编译单元实例化时,我会为它们中的每一个获得一个不同的类型,因此为DAC8568等获得一个namespace detail
立即解决了问题。
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 为什么常量成员可以初始化两次?
- 编译时检查是否有两个具有相同模板参数的模板实例化
- 如何获得C - 模板状行为,但仅允许实例化两种特定类型
- 当实例化两次时,C 提升线程会导致分割故障
- 析构函数在 lambda 捕获说明符中声明的类实例上运行两次
- 为什么将FMTFLAG指定两次 - 作为枚举的一部分,而另一个实例为静态const变量
- C++类成员初始化了两次
- 类成员的C++静态成员变量实例化了两次
- 静态成员变量初始化两次
- 如何在不实例化两个c++类型的情况下确定这两个类型
- 我应该在哪里声明一个涉及多次实例化的类的枚举类
- 只实例化派生类一次错误的 OOP
- 单例实例化了两次
- 如何通过定义派生类的构造函数来实例化两个基类的私有数据成员
- c++如何初始化对象?下面的情况是这样做两次吗
- 我可以检查哪些函数模板至少实例化过一次吗
- OpenGL缓冲两次初始化
- 一次只实例化一组类中的一个类,以节省内存
- 隔离和多次实例化进程中的C库