[ell-i-developers] Re: Problem with Array Initialization

  • From: Pekka Nikander <pekka.nikander@xxxxxx>
  • To: ell-i-developers@xxxxxxxxxxxxx
  • Date: Fri, 7 Mar 2014 02:25:39 +0200

> Some part of my code, I suppose some array, is generating some initialization 
> routine. From my previous experience with the SPI API, I know that if an 
> array is not declared adequately, gcc tries to generate code to actually copy 
> it (IIRC it requires memcpy that is not part of the arm-none-eabi standard 
> libraries).

My guess it is this line:

https://github.com/supramaterial/Runtime/blob/temp-enc-cpp-library/stm32/libraries/CoAP/arch/enc28j60/ENC28J60Class.h#L34

I think you want to have a reference there, not a copy:

  const SPIClass & spi_;

Note the ampersand.  In general I think having references is one of the more 
confusing things in C++.  While at the syntax level a reference looks like the 
thing itself, actually at the code level a reference works like a const 
pointer.  Of course, constant propagation can get rid of it, but if the 
compiler cannot do constant propagation on it, e.g. because the original is 
defined in a different compilation unit (and you are not using LLVM link-time 
optimisation), then the generated code will behave the same way as if you had 
had a constant pointer to the original instead of a reference.

In more concrete terms, these produce identical machine code:
  
  const SPIClass *const spi_;  ...   spi_->foo = bar;
  const SPIClass &      spi_;  ...   spi_.foo  = bar;

But if you are using a copy of the object instead of a reference, the generated 
code is quite different.  

But then, if you have a constant object (as you do here), and if the compiler 
knows the original at the compile time (which apparently is not the case here), 
then aggressive constant propagation may get rid of the constant object 
completely.  In that case you won't notice the difference.

The linker can fix a reference, as the compiler generates simply a symbol table 
entry for the reference.  To see how, use nm.  The linker cannot fix an object 
copy; for that the compiler generates a static initialiser.

I have made the same mistake a few times, in different disguises.  It is always 
hard to figure out the culprit.

> (1) Is there a concrete or specific way of declaring arrays and other 
> structures to make them allocated correctly and do not rely on initialisation 
> routines?

Well, no.  You have done most things right, including declaring the constructor 
constexpr.  However, there are no guarantees in C++ that the compiler will do 
the "right thing," i.e. compile-time initialisation.  The compiler is free to 
generate a startup-time call to the constructor aka a static initialiser.  
That's the reason why we have the assert in the linker script in the first 
place.

Or maybe there is, but the "way" is quite complicated, you basically have to 
know your compiler well enough. Then, we ware using (on the Mac) both LLVM and 
GCC, so you need to know them both. :-)

--Pekka

PS.  At least for the production version, I think it may be better to jump to 
the reset vector at abort() instead of having a busy loop, cf.

https://github.com/supramaterial/Runtime/blob/temp-enc-cpp-library/stm32/libraries/CoAP/arch/enc28j60/assert.h#L19

A busy loop and perhaps a blinking "LED" (i.e. GPIO line) at abort is better in 
a debug version.


Other related posts: