Is it safe to make a reference element to a constant of a temporary variable?

I tried to make the code as follows:

struct Foo
{
    double const& f;
    Foo(double const& fx) : f(fx)
    {
        printf("%f %f\n", fx, this->f); // 125 125
    }

    double GetF() const
    {
        return f;
    }
};
int main()
{
    Foo p(123.0 + 2.0);
    printf("%f\n", p.GetF()); // 0
    return 0;
}

But he does not fall at all. I also used valgrind to test the program, but there was no error or warning. Therefore, I assume that the compiler automatically generated code directing a link to another hidden variable. But I'm really not sure.

+4
source share
5 answers

No, this is not safe. More precisely, it is UB , meaning that everything is possible.

123.0 + 2.0 Foo, double fx. (.. Foo p(123.0 + 2.0);), f .

, f.

" ": , , , .

, $15.6.2/8 [class.base.init]

, mem- . [:

struct A {
  A() : v(42) { }   // error
  const int& v;
};

- ]

+4

. valgrind , .

, undefined. , -, , , .

valgrind , f , , valgrind . , 0, , , , .

, .

+3

const ?

, , "" . . ( )

, , , .

, , .

125 0, :

#include <alloca.h>
#include <cstring>
#include <iostream>
struct Foo
{
  double const& f;
  Foo(double const& fx) : f(fx)
  {
    std::cout << fx << " " << this->f << std::endl;
  }

  double GetF() const
  {
    return f;
  }
};

Foo make_foo()
{
  return Foo(123.0 + 2.0);
}

int main()
{
  Foo p = make_foo();
  void * const stack = alloca(1024);
  std::memset(stack, 0, 1024);
  std::cout << p.GetF() << std::endl;
  return 0;
}

0!


125.0 2.0 . rvalue, Foo, Foo double. .

, , , , Foo . , Foo, .

, , () . , , ( alloca memset), () , , . , segfault .


make_foo alloca std:: memset - , , "stack", , :

Foo p = Foo(123.0 + 2.0);
std::vector<unsigned char> v(1024, 0);
std::cout << p.GetF() << std::endl;
+1

, , .

, , undefined.

, . , 123.0 + 2.0, , Foo p(123.0 + 2.0). printf("%f\n", p.GetF()) , .

, undefined - , , . , , .

0

( undefined), asan AddressSanitizerUseAfterScope :

$ g++ -ggdb3 a.cpp -fsanitize=address -fsanitize-address-use-after-scope && ./a.out
125.000000 125.000000
=================================================================
==11748==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7fff1bbfdab0 at pc 0x000000400b80 bp 0x7fff1bbfda20 sp 0x7fff1bbfda18
READ of size 8 at 0x7fff1bbfdab0 thread T0
    #0 0x400b7f in Foo::GetF() const a.cpp:12
    #1 0x4009ca in main a.cpp:18
    #2 0x7fac0bd05d5c in __libc_start_main (/lib64/libc.so.6+0x1ed5c)
    #3 0x400808  (a.out+0x400808)

Address 0x7fff1bbfdab0 is located in stack of thread T0 at offset 96 in frame
    #0 0x4008e6 in main a.cpp:16

  This frame has 2 object(s):
    [32, 40) 'p'
    [96, 104) '<unknown>' <== Memory access at offset 96 is inside this variable

AddressSanitizerUseAfterScope, Clang 5.0 gcc 7.1.

Valgrind , , .

, double const& fx , 125.0. , - Foo p(123.0 + 2.0).

One way to make your code safe is to use cumulative life cycle extension ( Extending the life of a temporary resource through an rvalue data element works with an aggregate, but not with a constructor, why? ) By removing the constructor Foo::Foo(double const&)and changing the initializer pto use the list initialization syntax:

Foo p{123.0 + 2.0};
//   ^           ^
0
source

Source: https://habr.com/ru/post/1682550/


All Articles