Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does -O3 downcast/change the value of a const reference to a variable?

Tags:

c++

c++17

#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.

like image 232
ajoseps Avatar asked Aug 03 '21 17:08

ajoseps


1 Answers

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.

like image 197
Marek R Avatar answered Nov 17 '22 03:11

Marek R