I\'ve tried to code like this several times:
struct Foo
{
double const& f;
Foo(double const& fx) : f(fx)
{
printf(\"%f %f\\n\", f
This is indeed unsafe (it has undefined behavior), and the asan AddressSanitizerUseAfterScope will detect this:
$ 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) '' <== Memory access at offset 96 is inside this variable
In order to use AddressSanitizerUseAfterScope, you need to run Clang 5.0 or gcc 7.1.
Valgrind is good at detecting invalid use of heap memory, but because it runs on an unaltered program file it cannot in general detect stack use bugs.
Your code is unsafe because the parameter double const& fx
is bound to a temporary, a materialized prvalue double with value 125.0. This temporary has lifetime terminating at the end of the statement-expression Foo p(123.0 + 2.0)
.
One way to make your code safe is to use aggregate lifetime extension (Extending temporary's lifetime through rvalue data-member works with aggregate, but not with constructor, why?), by removing the constructor Foo::Foo(double const&)
, and changing the initializer of p
to use the list-initialization syntax:
Foo p{123.0 + 2.0};
// ^ ^