类成员的C++静态成员变量实例化了两次

C++ Static member variable of class member instantiated twice

本文关键字:两次 实例化 成员 C++ 静态成员 变量      更新时间:2023-10-16

我有一个模板类,它有两个静态成员变量,一个是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.cppDebugConsole.cpp中,这两个源都使用mux1extdac_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变量在每个实例化中出现一次,这是应该的,对于mux1channels也是如此。但是,对于extdac_sequencerchannels变量是重复的,我认为这解释了错误。

是我做错了什么,还是这是编译器或链接器错误?

编译器: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_framecommand_xx68channel_xx68都是在未命名的名称空间中定义的(当然,在标头中这样做是错误的)。这当然意味着,当从不同的编译单元实例化时,我会为它们中的每一个获得一个不同的类型,因此为DAC8568等获得一个

不同类型将未命名的命名空间更改为namespace detail立即解决了问题。

链接器输出中损坏的名称似乎完全相同,这一事实仍然让我有些困惑。怎么可能呢?

无论如何,我们从中了解到以下内容(其中一些我们已经知道):

  1. 我是simpleton-模式的实例化
  2. 标头中的未命名名称空间确实很糟糕
  3. 源自上述的错误可能相当微妙