I have a template class, which has two static member variables, one int
and another an std::array<volatile uint_fast32_t, 8>
. When I instantiate the template with two different classes (which are templates themselves) as template parameters, for one of the instantiations everything works perfectly, i.e. there is exactly one copy of both variables. However, for the other, the array appears in duplicate in the symbol table, and indeed my code has a bug that when I set a value in the array in one compilation unit, the change does not appear in another.
This is for an embedded system, which is the reason for this weird idiom of using static templates for a kind of compile-time polymorphism.
In code: Header declaring the class itself
//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
A header which distributes definitions of the hardware:
//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;
...
The header Hardware_types.h
is included in two source files, main.cpp
, DebugConsole.cpp
, both of which use both mux1
and extdac_sequencer
.
As for as I understand, based on answers such as this one and many others, the compiler should take care that each of the static member variables is instantiated exactly once for each instantiation of the template?
However, when I set the values of extdac_sequencer::channels
in DebugConsole.cpp
, the changes are not reflected in an interrupt handler declared in main.cpp
. The same works perfectly for mux1::channels
. Indeed, an excerpt from the symbol table, extracted from the .elf by 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
So the nextchan
variable appears once per instantiation, as it should, and for mux1
so does channels
. However, for extdac_sequencer
, the channels
variable is repeated, which I believe explains the bug.
Am I doing something wrong, or is this a compiler or linker bug?
Compiler: GCC arm-none-eabi 5.2.1 20151202
Linker: arm-none-eabi-ld 2.25.90.20151217
Linker options: -T mem.ld -T libs.ld -T sections.ld -nostartfiles -Xlinker --gc-sections -L"../ldscripts" -Wl,-Map,"Synth1Firmware.map" -Xlinker --cref --specs=nano.specs
I've narrowed down the conditions for this to happen:
If the first template parameter of dacmux
is not itself a template, everything works, i.e. no duplicate symbol:
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;
However, if the template parameter is templated itself, I get the duplicate symbol problem:
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;
Here, extdac1 is itself, again, a template:
typedef HAL::DAC8568<dacspi, typename dacspi::nss> extdac1;
...and dacspi
is a template, and so on. Also, in the case which does work, with the other instantation, while dac_write_sequence_t
is a template, it isn't anymore a template of templates. So I'm starting to think that this is a problem with template recursion depth, i.e. ld
isn't looking deep enough.
A further interesting observation: on exactly the same condition as having the duplicate symbol, the Eclipse syntax highlighter says "invalid template parameters" on the line declaring extdac_sequencer
, although the actual compilation step goes through.
Turns out this was my sillyness: I was using an unnamed namespace in a header that defines the template HAL::DAC8568, which is defined as
template<typename spi_t, typename nss_t> using DAC8568 = ti_dac<spi_t, nss_t,
xx68_frame,
command_xx68,
channel_xx68>;
Here xx68_frame
, command_xx68
, and channel_xx68
are all defined in the unnamed namespace (which is of course the wrong thing to do in a header). This of course means that when instantiated from a different compilation unit, I get a different type for each of them and therefore a different type for DAC8568 and so on, so it's perfectly natural to get another instance of the static variable.
Changing the unnamed namespace to namespace detail
fixed the problem immediately.
I am still somewhat baffled by the fact that the mangled names in the linker output seem identical. How can that be?
Anyway, we learn from this the following (some of which we knew already):
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With