First I'll start by saying the question is maybe wrong, as I am not sure what is the issue with the following code.
#include <array>
#include <cstdint>
#include <iostream>
template <typename T>
struct PixelRGB {
T r {};
T g {};
T b {};
};
using PixelRGBui8 = PixelRGB<uint8_t>;
template <typename Pixel_T>
class FrameRowView {
public:
FrameRowView(const uint32_t& width)
: m_width { width }
{
}
const uint32_t& m_width;
};
double bar(auto& view)
{
std::array<PixelRGBui8, 128> arrayA = {};
std::cout << "width: " << view.m_width << " " << arrayA.data() << "\n";
return 42.0;
}
void foo()
{
static constexpr auto Width = 16;
auto frameRowPrev = FrameRowView<PixelRGBui8> { Width };
auto val = bar(frameRowPrev);
std::cout << "frameRowPrev: " << frameRowPrev.m_width << "\n";
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
foo();
return EXIT_SUCCESS;
}
and on compiler explorer
Now the expected output is width: 16.
Clang in -O0 and -O3 output the right answer but in -O0 ASAN gives me an error about stack-use-after-scope.
==1==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7fe67e200040 at pc 0x55c0732b8ac1 bp 0x7fff5d95d590 sp 0x7fff5d95d588
READ of size 4 at 0x7fe67e200040 thread T0
#0 0x55c0732b8ac0 in double bar<FrameRowView<PixelRGB<unsigned char>>>(FrameRowView<PixelRGB<unsigned char>>&) /app/example.cpp:27:34 #1 0x55c0732b8621 in foo() /app/example.cpp:36:14 #2 0x55c0732b874a in main /app/example.cpp:43:3 #3 0x7fe6807190b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2) (BuildId: 9fdb74e7b217d06c93172a8243f8547f947ee6d1) #4 0x55c0731e137d in _start (/app/output.s+0x2137d)
Address 0x7fe67e200040 is located in stack of thread T0 at offset 64 in frame #0 0x55c0732b84ff in foo() /app/example.cpp:32
This frame has 2 object(s):
[32, 40) 'frameRowPrev' (line 35) [64, 68) 'ref.tmp' (line 35) <== Memory access at offset 64 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions are supported)
MSVC in /O0 output the right answer but in /O2 output garbage. Actually if you play with the size of the array named arrayA in the function bar and set it to 1 the output is actually good.
I am tempted to say it is a false positive on ASAN and a bug from MSVC? But it could be my bug too.
Does anybody know the answer to this riddle?
Because 16
is int
by default in C++ standard, Width
is an int
variable; then you pass it as a const uint32_t&
parameter, so possibly it causes a copy to make a temporary uint32_t
variable, and pass it to this const
reference. After the function ends, this temporary variable is destructed, so your reference refers to an invalid variable, which causes undefined behavior.
See https://godbolt.org/z/GqzvYPW47, and as you make Width
a uint32_t
or an int
variable, it will call different ctor for rvalue and const lvalue differ there.
If you make Width
a uint32_t
variable, it will be definitely right.