Puzzling introduction of temporary in `* const&` on older C++ compilers

51 views Asked by At

When building on a platform with an older C++ compiler, I noticed unexpected behavior with code that worked fine elsewhere. I'm not sure whether this indicates a bug in older compilers, or some change in the standard, but I have been explicitly building with C++11.

In particular, the older compiler seems to use a temporary for a * const& when I wouldn't expect it to, and that temporary leads to a SIGSEGV when its stack frame gets cleaned up and overwritten.

Here is my best effort at distilling a MWE from the original code. My problem revolves around the constructor of class C:

#include <stdio.h>

struct A {
    int* i;
};

class B {
public:
    int* const& i_ptr_const_ref; // normally not public
    B(int* const& i_ptr) : i_ptr_const_ref(i_ptr) {}
    operator int* const& (void) const { return i_ptr_const_ref; }
};

int null_b = { 0 };
int* null_b_ptr = &null_b;
int* const& null_b_ptr_const_ref = null_b_ptr;

class C {
public:
    B b;
//  C(const A* a_ptr) : b(a_ptr ? a_ptr->i : null_b_ptr_const_ref) {} // this works
    C(A* a_ptr) : b(a_ptr ? a_ptr->i : null_b_ptr_const_ref) {} // this fails
//  C(A* a_ptr) : b(a_ptr ? (int* const&) a_ptr->i : null_b_ptr_const_ref) {} // this works
//  C(A* a_ptr) : b(a_ptr->i) {} // this works
};

int main(void) {
    A a;
    A* a_ptr = &a;
    a_ptr->i = (int*) 42;
    C c(a_ptr);
    printf("c.b.i_ptr_const_ref = %p\n", (void*) c.b.i_ptr_const_ref);
    printf("c.b=                  %p\n", (void*) c.b);
    printf("a_ptr->i=             %p\n", (void*) a_ptr->i);
    return 0;
}

Try it on Compiler Explorer

The values that I print out should all match, but on GCC compilers prior to 5.1 and ICC compilers prior to 18 (I understand that Intel prides itself on "bug-for-bug" compatibility with other compilers), the middle one shows a stack address instead of the expected value. All versions of Clang and ELLCC compilers that I was able to try behave correctly.

The uncommented C constructor is the one I want to use, but it doesn't work correctly. I get the expected result in the MWE if I make the A* a_ptr parameter const, but in the larger code base I can't do that. I also get the expected result if I don't use a ?: in the initializer, or if I explicitly cast a_ptr->i in the initializer as int* const&, but I don't understand why I should have to.

I would have thought that initializing an int* const& with an int* would be fine, but my best guess is that the ?: somehow confused the compiler. Can anybody help me understand whether the older GCC and ICC compilers are incorrect here, or whether there is something about the language that I'm misunderstanding?

1

There are 1 answers

0
Davis Herring On BEST ANSWER

This is pretty clearly a compiler bug: certainly an lvalue of type int* can be implicitly converted to int* const&, so the result of the conditional operator should be an lvalue.

The printf output appears mysterious, but in fact is a straightforward consequence of the bug: even the first read of c.b.i_ptr_const_ref (which is of course a reference that is transparently followed) is reading from the dead temporary, but it has yet to be overwritten and still contains a copy of a.i. After the first printf, that memory has been clobbered and happens to hold a stack address instead.