Board won't work if object is not referenced with poiter

I write a basic container class encapsulates some other classes, to read my GY89 sensor via I2C. Unfortunately, I just stumbled with a black magic kind of situation.

The code is like below,

GY89.ino

#include "GY89.h"

GY89 sensor;

void setup()
{
    Serial.begin(112500);
    Serial.println("begin 112500");
}

void loop()
{
    sensor.print_all();
    delay(100);
}

GY89.h

#pragma once

#include <Arduino.h>
#include "L3GD20.hpp"

class GY89 {
private:
    L3GD20 *gyro;

public:
    GY89() {
        gyro = new L3GD20;
    }

    ~GY89() {
        delete gyro;

    void print_all() {
        gyro->read();
        Serial.print(gyro->temperature);
        Serial.print(",");
        Serial.print(gyro->rate.x);
        Serial.print(",");
        Serial.print(gyro->rate.y);
        Serial.print(",");
        Serial.print(gyro->rate.z);
    }
};

If I upload this code, Mega R3 board gets stuck, cannot print/read from serial nor can update digital pins. But if change the third line in GY89.ino to GY89 *sensor; everything starts to work again.

There is no compilation errors, but board kills itself, only the GND and Vdd pins work. Hell, I'm confused...

EDIT:

The same issue happens if I directly create L3GD20 class instance, I could have made a mistake while writing the driver, code as follows.

GY89.ino

[code]#include "GY89.h"

L3GD20 gyro;

void setup()
{
    Serial.begin(112500);
    Serial.println("print something!!!");
}

void loop()
{
    Serial.println("just nothing...");
}

[/code]

Like if I change it to L3GD20 *gyro;, it works...

L3GD20.hpp

#pragma once

#include <Arduino.h>
#include <Wire.h>

class L3GD20 {
public:    
    enum REG_ADDR {
       WHO_AM_I       = 0x0F,
       CTRL_REG1      = 0x20,
       CTRL_REG2      = 0x21,
       CTRL_REG3      = 0x22,
       CTRL_REG4      = 0x23,
       CTRL_REG5      = 0x24,
       REFERENCE      = 0x25,
       OUT_TEMP       = 0x26,
       STATUS_REG     = 0x27,
       OUT_X_L        = 0x28,
       OUT_X_H        = 0x29,
       OUT_Y_L        = 0x2A,
       OUT_Y_H        = 0x2B,
       OUT_Z_L        = 0x2C,
       OUT_Z_H        = 0x2D,
       FIFO_CTRL_REG  = 0x2E,
       FIFO_SRC_REG   = 0x2F,
       INT1_CFG       = 0x30,
       INT1_SRC       = 0x31,
       INT1_THS_XH    = 0x32,
       INT1_THS_XL    = 0x33,
       INT1_THS_YH    = 0x34,
       INT1_THS_YL    = 0x35,
       INT1_THS_ZH    = 0x36,
       INT1_THS_ZL    = 0x37,
       INT1_DURATION  = 0x38
    };

    enum POWER_MODE {
        power_down,
        power_up,
        sleep
    };

    enum SA0_STATE {
        sa0_low,
        sa0_high,
        sa0_auto
    };
    
    enum FS_RATE {
        fs250,
        fs500,
        fs2000
    };
    
    enum ODR {
        odr95  = 0x00,
        odr190 = 0x60,
        odr380 = 0x80,
        odr760 = 0xC0
    };
    
    enum BANDWIDTH {
        bw0 = 0x00,
        bw1 = 0x10,
        bw2 = 0x20,
        bw3 = 0x30
    };
    
    enum HP_FILTER_MODE {
        hp_normal_reset = 0x00,
        hp_ref_signal   = 0x10,
        hp_normal       = 0x20,
        hp_autoreset    = 0x30
    };
    
    enum HP_FILTER_CUTOFF {
        hp_cof0 = 0x00,
        hp_cof1 = 0x01,
        hp_cof2 = 0x02,
        hp_cof3 = 0x03,
        hp_cof4 = 0x04,
        hp_cof5 = 0x05,
        hp_cof6 = 0x06,
        hp_cof7 = 0x07,
        hp_cof8 = 0x08,
        hp_cof9 = 0x09,
    };

    typedef struct { int16_t x, y, z; } vector;

    vector rate;
    uint8_t temperature;

    L3GD20();

    void read();
    void set_power(const POWER_MODE&) const;
    void set_bandwidth(const BANDWIDTH&) const;
    void set_filter_mode(const HP_FILTER_MODE&, const HP_FILTER_CUTOFF&) const;
    void enable_filter(const bool&) const;

    explicit operator bool() const { return address; }

private:
    uint8_t address;
    uint8_t axis_status;

    bool find_address();
    void write_reg(const REG_ADDR&, const uint8_t&) const;
    
    uint8_t read_reg(const REG_ADDR&) const;
};

L3Gd20.cpp

#include "L3GD20.hpp"

L3GD20::L3GD20()
{
    if (!find_address()) {
        address = 0;
        return;
    }

    // 95 ODR & 12.5 BW, all axis power on
    write_reg(CTRL_REG1, 0x0F);

    // Full scale 250
    write_reg(CTRL_REG4, 0x20);
}

bool L3GD20::find_address()
{
    uint8_t SA0_LO_ADDR = 0x6A;
    uint8_t SA0_HI_ADDR = 0x6B;

    Wire.beginTransmission(SA0_LO_ADDR);
    Wire.write(WHO_AM_I);

    if (Wire.endTransmission()) {
        Wire.beginTransmission(SA0_HI_ADDR);
        Wire.write(WHO_AM_I);

        if (Wire.endTransmission()) {
            return false;
        } else {
            address = SA0_HI_ADDR;
        }
    } else {
        address = SA0_LO_ADDR;
    }

    Wire.requestFrom(address, (byte) 1);

    if (Wire.available()) {
        Wire.read(); // dump whoami
    } else {
        return false;
    }

    return true;
}

uint8_t L3GD20::read_reg(const REG_ADDR &reg) const
{
    Wire.beginTransmission(address);
    Wire.write(reg);
    
    if (!Wire.endTransmission()) {
        Wire.requestFrom(address, (byte) 1);
        return Wire.read();
    }

    return 0;
}

void L3GD20::write_reg(const REG_ADDR &reg, const uint8_t &val) const
{
    Wire.beginTransmission(address);
    Wire.write(reg);
    Wire.write(val);
    Wire.endTransmission();
}

void L3GD20::read()
{
    Wire.beginTransmission(address);
    Wire.write(OUT_TEMP | (1U << 7));
    Wire.endTransmission();
    Wire.requestFrom(address, (byte) 8);

    temperature = 45 - Wire.read();
    axis_status = Wire.read();
    uint8_t xl  = Wire.read();
    uint8_t xh  = Wire.read();
    uint8_t yl  = Wire.read();
    uint8_t yh  = Wire.read();
    uint8_t zl  = Wire.read();
    uint8_t zh  = Wire.read();

    // combine high and low bytes
    rate.x = (int16_t)((xh << 8U) | xl);
    rate.y = (int16_t)((yh << 8U) | yl);
    rate.z = (int16_t)((zh << 8U) | zl);
}

void L3GD20::set_power(const POWER_MODE &mode) const
{
    uint8_t val = read_reg(CTRL_REG1) & 0xF0;

    switch (mode) {
             case power_down: val |= 0x00; break;
             case sleep:      val |= 0x08; break;
    default: case power_up:   val |= 0x0F; break;
    }

    write_reg(CTRL_REG1, val);
}

void L3GD20::set_bandwidth(const BANDWIDTH &bw) const
{
    write_reg(CTRL_REG1, (read_reg(CTRL_REG1) & 0xCF) | bw);
}

void L3GD20::set_filter_mode(const HP_FILTER_MODE &mode, const HP_FILTER_CUTOFF &cof) const
{
    write_reg(CTRL_REG2, (mode | cof) & 0x3F);
}

void L3GD20::enable_filter(const bool &enable) const
{
    write_reg(CTRL_REG5, (read_reg(CTRL_REG5) & 0xEF) | (((uint8_t)enable) << 4));
}

I haven't looked too deeply at it, but in principle, you should avoid referencing objects in a constructor which are dependent on the Arduino environment because this is initialised relatively late compared with the execution of the constructor.
You appear to be referencing the Wire library from the L3GD20 constructor.
The usual work around is to have a begin() method where you can control when that initialisation takes place, say in setup().

So that explains why it works if I crate a new object with pointer reference in setup(), but I still don't really catch why, is wire library initializes itself after setup()? why it would not work if I create objects before it?

I'm not sure what the dependencies are in that specific case. However, it is the init() function here: ArduinoCore-avr/wiring.c at master · arduino/ArduinoCore-avr · GitHub which sets up the Arduino environment, including hardware timers etc. That all runs before setup().

Thank you, I always check twice with c++. So powerful yet utterly abrupt.

Perhaps you meant "Serial.begin(115200);" instead of "Serial.begin(112500);". I don't think Serial Monitor has 112500 in its supported baud rates.