0

I have a header file defined as

#pragma once
#include <iostream>

template<int size>
struct B
{
    double arr[size * size];

    constexpr B() : arr()
    {
        arr[0] = 1.;
    }
};

template<int size>
struct A
{
    const double* arr = B<size>().arr;

    void print()
    {
        // including this statement also causes undefined behaviour on subsequent lines
        //printf("%i\n", arr);

        printf("%f\n", arr[0]);
        printf("%f\n", arr[0]); // ???

        // prevent optimisation
        for (int i = 0; i < size * size; i++)
            printf("%f ", arr[i]);
    }
};

and call it with

auto a = A<8>();
a.print();

Now this code only runs expectedly when compiled with msvc release mode (all compiled with c++17).

expected output:

1.000000
1.000000

msvc debug:

1.000000
-92559631349317830736831783200707727132248687965119994463780864.000000

gcc via mingw (with and without -g):

1.000000
0.000000

However, this behaviour is inconsistent. The expected output is given if I replace double arr[size * size] with double arr[size] instead. No more problems if I allocate arr on the heap of course.

I looked at the assembly of the msvc debug build but I don't see anything out of the ordinary. Why does this undefined behaviour only occur sometimes?

asm output

decompiled msvc release

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
mmmm
  • 16
  • 4
  • 3
    `const double* arr = B().arr;` creates dangling pointer. – Jarod42 Mar 10 '22 at 11:38
  • "_Why does this undefined behaviour only occur sometimes?_": It is always UB, but causing problems only sometimes lies in the nature of UB. – user17732522 Mar 10 '22 at 11:40
  • I'm aware that it should create a dangling pointer due to stack allocation. But with a smaller array, `double arr[size]`, consistently provides the "expected" output no matter how it's compiled. I was wondering why this behaviour differed depending on the allocation size. – mmmm Mar 10 '22 at 12:02
  • *I was wondering why this behaviour differed depending on the allocation size.* If the array is stored on the stack, the next call to printf will likely reuse some of that space. – BoP Mar 10 '22 at 12:36
  • **Undefined behavior**: if you're lucky, the program will crash. If you're unlucky, the program will work as you expected. Appears you got unlucky on this one. – Eljay Mar 10 '22 at 13:38

3 Answers3

1

In this declaration

const double* arr = B<size>().arr;

there is declared a pointer to (the first element of ) a temporary array that will not be alive after the declaration

So dereferencing the pointer results in undefined behavior.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

It seems that it was completely coincidental that smaller allocations were always addressed in a spot that would not get erased by the rep stosd instruction present in printf. Not caused by strange compiler optimisations as I first thought it was.

What does the "rep stos" x86 assembly instruction sequence do?

I also have no idea why I decided to do it this way. Not exactly the question I asked but I ultimately wanted a compile time lookup table so the real solution was static inline constexpr auto arr = B<size>() on c++20. Which is why the code looks strange.

mmmm
  • 16
  • 4
0

When you wrote:

const double* arr = B<size>().arr;

The above statement initializes a pointer to const double i.e., const double* named arr with a temporary array object. Since this temporary array object will be destroyed at the end of the full-expression, using arr will lead to undefined behavior.

Why does this undefined behaviour only occur sometimes?

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.

So the output that you're seeing(maybe seeing) is a result of undefined behavior. And as i said don't rely on the output of a program that has UB. The program may just crash.

So the first step to make the program correct would be to remove UB. Then and only then you can start reasoning about the output of the program.


1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.

Jason
  • 36,170
  • 5
  • 26
  • 60