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

时间:2016-05-29 13:30:07

标签: c++ templates linker

我有一个模板类,它有两个静态成员变量,一个是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

据我所知,基于this one等许多其他答案,编译器应该注意每个静态成员变量是否只为模板的每个实例化实例化一次?

但是,当我在extdac_sequencer::channels中设置DebugConsole.cpp的值时,更改不会反映在main.cpp中声明的中断处理程序中。这同样适用于mux1::channels。实际上,从objdump -t

中提取的符号表的摘录
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-T mem.ld -T libs.ld -T sections.ld -nostartfiles -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"Synth1Firmware.map" -Xlinker --cref --specs=nano.specs变量会重复,我相信这会解释错误。

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

编译:GCC arm-none-eabi 5.2.1 20151202

Linker:arm-none-eabi-ld 2.25.90.20151217

链接器选项: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;

但是,如果模板参数本身是模板化的,我会得到重复的符号问题:

typedef HAL::DAC8568<dacspi, typename dacspi::nss> extdac1;

在这里,extdac1本身也是一个模板:

dacspi

...而dac_write_sequence_t是一个模板,依此类推。此外,在确实有效的情况下,使用其他即时功能,虽然ld是模板,但它不再是模板的模板。所以我开始认为这是模板递归深度的问题,即extdac_sequencer看起来不够深。

另一个有趣的观察:在与具有重复符号完全相同的条件下,Eclipse语法高亮显示器在声明Microsoft.CodeDom.Providers.DotNetCompilerPlatform的行上显示“无效模板参数”,尽管实际的编译步骤已经完成。

1 个答案:

答案 0 :(得分:2)

原来这是我的愚蠢:我在标题中使用了一个未命名的命名空间,定义了模板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 -pattern
  2. 的实例化
  3. 标题中未命名的名称空间真的很邪恶
  4. 源于上述的错误可能非常微妙