6
#include <iostream>
#include <stdint.h>

class Test {
  public:
  Test(const int64_t & val) : val_(val) {
    std::cout << "initialized: " << val_ << std::endl;
  }
  void print() {std::cout << "reference val: " << val_ << std::endl;}

  private:
  const int64_t & val_;
};


int main() {
  long long int input_val= 1628020800000000000L;
  auto t = Test(input_val);
  std::cout << "input_val: " << input_val << std::endl; 
  t.print();
}

If you build without an optimized build you get the following:

g++ main.cpp -std=c++17
initialized: 1628020800000000000
input_val: 1628020800000000000
reference val: 1628020800000000000

If you build with an optimized build e.g. -O3, you get the following:

g++ main.cpp -std=c++17 -O3
initialized: 1628020800000000000
input_val: 1628020800000000000
reference val: 0

I'm guessing this difference is due to the casting/treatment of input_val being of type long long int, but I do not really understand why that's the case. I thought a long was at least 32bit and a long long was at least twice as wide as a long long. Since the reference is a const int64_t, I thought there wouldn't be any casting issues.

I realize if I switch the long long int to an int64_t, this wouldn't be an issue, but I want to learn the reason as to why this happens.

g++/gcc version:

g++ (GCC) 9.1.1 20190605 (Red Hat 9.1.1-2) Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

EDIT: The original version of this code had input_val (original static_val) as a global static variable. I changed the example to be simpler since the issue is not predicated on the variable being a global static var.

ajoseps
  • 1,871
  • 1
  • 16
  • 29

1 Answers1

11

Problem is conversion.

int64_t is not the same thing as long long int. So when this constructor is called: Test(const int64_t & val) temporary value of type int64_t is created from long long int.

So result is const int64_t & val_; holds reference to temporary object which lifetime ends before Test::print is called. Address sanitizer finds issue quite nicely.

When you unify types it works: https://godbolt.org/z/nGKa6azE8

Note I've added to check what type of int64_t is and it matches long int when building for 64 bits and long long int for 32 bit build.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • Why does it work in a debug build vs an optimized build? The debug build just holds the reference for a longer period of time? – ajoseps Aug 03 '21 at 17:36
  • 6
    @ajoseps • doing it was [**undefined behavior**](https://stackoverflow.com/a/4105123/4641116), and once you have that anything is possible. Even appearing to work correctly. – Eljay Aug 03 '21 at 17:37
  • 3
    cppreference says that `int64_t` is a `typedef` but It behaves as a distinct type with the same size as `long long int` (on [godbolt](https://godbolt.org/z/3nrYcexMT)). This was a surprise to me. – Ted Lyngmo Aug 03 '21 at 17:43
  • 2
    Ah ... got it. The `typedef` in this case is to `long int` which has the same size but is a distinct type (not the same as `long long int`) – Ted Lyngmo Aug 03 '21 at 17:51
  • 1
    @TedLyngmo Meanwhile over on my Windows machine, `std::int64_t` typedefs to `long long` and your code sample fails to compile because it refuses to let me redefine a function. LP64 vs LLP64, I suppose. [cppreference](https://en.cppreference.com/w/cpp/language/types) has a handy table folks may be interested in. – Nathan Pierson Aug 03 '21 at 17:55