【发布时间】: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.cpp、DebugConsole.cpp中,这两个文件都使用mux1和extdac_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 变量在每次实例化时出现一次,这是理所当然的,对于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 的行上显示“无效的模板参数”,尽管实际的编译步骤已经完成。
【问题讨论】:
-
我会说这是一个链接器问题,而不是编译器问题。编译器完成了它的工作,并在每个翻译单元中生成了符号。在链接时丢弃重复的符号是链接器的工作。
-
您是在构建单个可执行文件还是一堆共享对象?
-
@nm 我正在构建一个 .elf,然后将其刷入固件
-
@SamVarshavchik 你可能是对的。关于如何使链接器行为正确的任何想法?
-
在这种情况下,我会开始随机更改,试图找出触发错误行为的原因。从更改对象模块链接的顺序到更改符号名称的长度。最后,
binutilsbugzilla 出现在大厅的尽头,右边最后一扇门。