Home > Back-end >  Segmentation fault with C and consteval
Segmentation fault with C and consteval

Time:12-22

The following small example of C 20 code gives a segmentation fault when run. Why?

If I create an object of class Implementation and call the consteval function implementation.foo() it returns 42 as expected. However if I create a reference of type Interface and call the consteval function interface.foo() I get a segmentation fault. I am missing something in why this would happen.

// Compile with
//
// g   -std=c  20 -Werror -Wall -Wextra -Wpedantic consteval.cpp -o consteval

#include <iostream>

struct Interface
{
        virtual consteval int foo(void) = 0;
};

struct Implementation final : public Interface
{
        consteval int foo(void) override { return 42; }
};

int main(void)
{
        Implementation implementation;

        std::cout << "implementation.foo() returns: " << implementation.foo() << std::endl;

        Interface& interface = implementation;

        std::cout << "interface.foo() returns: " << interface.foo() << std::endl;

        return 0;
}

Link to Compiler Explorer

CodePudding user response:

Your code should not compile. I believe it is a bug in GCC that it tries to compile it and the weird segfault is just a consequence.

foo is consteval, so every usage of it must be part of a constant expression. But here you use it as interface.foo(), where interface is not a core constant expression (and foo is not static), so interface.foo() can't possibly be a constant expression. Thus your code is invalid and should simply fail with a compiler error (but should neither segfault nor "work" as it did in the comments; that's just GCC's fault).

If you correct the code to make the call valid, then you should get the right result. You could, say, wrap the thing in a constexpr function

consteval int example() {
   Implementation impl;
   Interface &intf = impl;
   return intf.foo();
}
int main() { std::cout << example() << "\n"; }

This works fine on current GCC.

Another way to get to correct code is to make the Implementation object constexpr static and foo const-qualified:

struct Interface {
        virtual consteval int foo(void) const = 0; // constexpr objects are const, so need const-qual to call foo
};

struct Implementation final : public Interface {
        consteval int foo(void) const override { return 42; }
};
int main() {
    constexpr static Implementation impl; // need static to form constant expression references ("the address of impl is now constexpr")
    Interface const &intf = impl; // this reference is automatically usable in constant expressions (being of reference type and constant-initialized), note that it is to a *const* Interface, requiring the change to foo
    std::cout << intf.foo() << "\n";
}

GCC still chokes on this one and produces a segfault, but Clang produces the expected result. Again, I suspect a GCC bug.

  • Related