【问题标题】:C++ Static member variable of class member instantiated twiceC++类成员的静态成员变量实例化了两次
【发布时间】:2016-05-29 13:30:06
【问题描述】:

我有一个模板类,它有两个静态成员变量,一个是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 等许多其他答案,编译器应该注意每个静态成员变量对于模板的每次实例化都只实例化一次?

但是,当我在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 的行上显示“无效的模板参数”,尽管实际的编译步骤已经完成。

【问题讨论】:

  • 我会说这是一个链接器问题,而不是编译器问题。编译器完成了它的工作,并在每个翻译单元中生成了符号。在链接时丢弃重复的符号是链接器的工作。
  • 您是在构建单个可执行文件还是一堆共享对象?
  • @nm 我正在构建一个 .elf,然后将其刷入固件
  • @SamVarshavchik 你可能是对的。关于如何使链接器行为正确的任何想法?
  • 在这种情况下,我会开始随机更改,试图找出触发错误行为的原因。从更改对象模块链接的顺序到更改符号名称的长度。最后,binutils bugzilla 出现在大厅的尽头,右边最后一扇门。

标签: c++ templates linker


【解决方案1】:

原来这是我的愚蠢行为:我在定义模板 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. 由上述引起的错误可能非常微妙

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 2015-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多