(Solved: tablejump does it) Class constructor declaration does not call function

Wow, here’s a really big one for anyone using LiquidCrystal, or pretty much any library that relies on adding parameters to its declaration (e.g. “LiquidCrystal lcd(rs,enable,d4,d5,d6,d7);”)… through the miracles of disassembly, it’s revealed that these constructor functions never get called at all! They exist in the code, but nothing ever references or flows into them… they’re just completely-avoided memory areas if the function isn’t explicitly defined within a function. It makes sense, after all, if there’s nowhere in the program flow that it’s to be run…


class LiquidTWI : public Print {
	LiquidTWI(uint8_t i2cAddr);
... }
LiquidTWI::LiquidTWI(uint8_t i2cAddr) {
	if (i2cAddr > 7) i2cAddr = 7;
	_i2cAddr = i2cAddr; // transfer this function call's number into our internal class state
	_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; // in case they forget to call begin() at least we have something
LiquidTWI lcd(0);

And here’s the disassembly from “avr-objdump -d -C i2c_perftest.cpp.elf > i2c_perftest.cpp.txt”…

000000ae <.do_global_ctors_start>:

ae: c8 36       cpi r28, 0x68 ; 104
 b0: d1 07       cpc r29, r17
 b2: c9 f7       brne .-14     ; 0xa6 <.do_global_ctors_loop>
 b4: 0e 94 f5 05 call 0xbea ; 0xbea
 b8: 0c 94 61 07 jmp 0xec2 ; 0xec2 <_exit>
000000bc <__bad_interrupt> (omitted)…
000000c0 :
 c0: 8a e4       ldi r24, 0x4A ; 74
 c2: 91 e0       ldi r25, 0x01 ; 1
 c4: 60 e0       ldi r22, 0x00 ; 0
 c6: 0e 94 4f 01 call 0x29e ; 0x29e <LiquidTWI::LiquidTWI(unsigned char)>
 ca: 08 95       ret

Take a look at .do_global_ctors_start: it just jumps straight from initialization into main()! And main() never does anything with these constructors, neither do the first references to “lcd” functions either (in case of compiler optimization). The address 0xc0 is never even referenced in any way. Neither is 0x29e. The constructor function never, ever gets touched.

It may appear that constructors are doing their job, but most variables in constructors like LiquidCrystal equate to “0” in the code (like “_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;” = 0 | 0 | 0 == 0), and the pinMode is always set on every write operation in the original code, so this seems to have eluded Arduino coders for as long as LiquidCrystal has been around…

Although I still can’t account for how on earth the pin-numbers actually get passed into the class when the constructor is responsible (in init()) for transferring those into the class’s memory… that remains a mystery. But I spent a good hour or so trying to figure out why nothing was happening on the i2c GPIO chip when I had most of the init stuff in the constructor :wink:

class LiquidTWI : public Print {
    LiquidTWI(uint8_t i2cAddr);
... }


Yeah, that's how the original LiquidCrystal declares them in the header file:

class LiquidCrystal : public Print {
  LiquidCrystal(uint8_t rs, uint8_t enable,
        uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
        uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);
  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);

  void clear();
  void home();

  void noDisplay();
  void display();
  void noBlink();

... Oh, maybe the missing semicolon at the end of the bracket? Yeah, I left that out of the quoted version, sorry. It's in the original .h file I'm compiling that from. :)

For detective work, I suggest you use a really simple example. Make a single change and compare object dump:

// Blink sketch using static storage duration "global" struct.
// Not intended as an example of "good programming practices," but
// as a simple example to compare outpute from avr-objdump
// Run avr-objdump -d -C PathAndFileNameOfSketch.cpp.elf > BlinkDump13.txt
// Then change the object creation line to foo(12) or some such thing, and save
// the objdump to a file named BlinkDump12.txt
// Look at the difference: a single byte value 0x0D versus 0x0C in
// the line after
//  00000xxx <global constructors keyed to foo>:
// Now, see if you can discover how the run-time code invokes the constructor
// with a parameter...
//  davekw7x

struct Foo
    int ledPin;
    Foo(int a) : ledPin(a) {}

// The constructor for Foo is invoked before the C++ main() program
Foo foo(13);

// C++ main() calls Arduino init() and then calls this setup() function.
void setup()
    pinMode(foo.ledPin, OUTPUT); // Set led pin as OUTPUT 

// This loop() function is called repeatedly from inside an infinite loop in C++ main()
void loop()                     
    digitalWrite(foo.ledPin, HIGH); // LED on
    delay(1000);                    // Wait for a second

    digitalWrite(foo.ledPin, LOW);  // LED off
    delay(1000);                    // Wait for a second



Good example! It does indeed show that the value changes in the compiled code. However, the key issue still remains: that code is never executed in the final ASM program. Seriously, follow the code around and you can see where it goes…

Disassembly of section .text:
00000000 <__vectors>: (CPU starting point)
   0:	0c 94 62 00 	jmp	0xc4	; 0xc4 <__ctors_end>
---> 0xC4 -->
000000c4 <__ctors_end>: (skipping the actual code here...)
  da:	02 c0       	rjmp	.+4      	; 0xe0 <.do_copy_data_start>
---> 0xE0 -->
000000e0 <.do_copy_data_start>:
  ec:	01 c0       	rjmp	.+2      	; 0xf0 <.do_clear_bss_start>
---> 0xF0 -->
000000f0 <.do_clear_bss_start>:
  fc:	04 c0       	rjmp	.+8      	; 0x106 <.do_global_ctors_start>
---> 0x106 -->
00000106 <.do_global_ctors_start>:
 10a:	c9 f7       	brne	.-14     	; 0xfe <.do_global_ctors_loop>
---> 0xfe -->
000000fe <.do_global_ctors_loop>:
 102:	0e 94 83 01 	call	0x306	; 0x306 <__tablejump__>
---> 0x306 -->
00000306 <__tablejump__>:
 30c:	09 94       	ijmp
---> ?? (I'm guessing this returns across a long jump) -->
 10c:	0e 94 f7 00 	call	0x1ee	; 0x1ee <main>
---> main() --> END

See? It just initializes the CPU using the standard set of commands, and never touches the “0000011a :” section that’s right below that section (since main() never returns).

Now, I am curious about the “tablejump” subroutine… there’s an “ijmp” command at the end that really makes me wonder…

Aha! OK, I'm the dummy. We figured as much, right? :grin:

OK, so after hurting my head trying to follow the program and register-shifting around, for lack of any proper AVR debugger packaged with WinAVR (grr...), I finally figured it out.

  • do_global_ctors is what calls the "global ConsTructORS" (ctors) functions using a "jump table".
  • AVR uses word memory addresses, so a byte of "8d" in code is really byte address "11a" in the real world.
  • __ctors_start is a zero-terminated list of constructors to be called.
  • tablejump is used to call each of these constructors; the constructors themselves have a "ret" that returns from the tablejump subroutine.

So they do actually get executed as real code. Just in a very confusing, mind-bending sort of way. I thought it would be odd of the avr-gcc compiler to just leave unused segments lingering around!

Well, now I can use this knowledge to figure out why the heck, really, the constructor functions weren't doing their jobs...

Well, now I can use this knowledge to figure out why the heck, really, the constructor functions weren't doing their jobs...

Since talking to yourself on the forum seems to have helped you figure out one issue, perhaps describing the real problem you were trying to solve, and posting some code, will lead you to figure out the real problem while we listen in.