1

Below is the code I have. I stripped stuff not related to the issue to keep this concise:

IChip.hpp (Root abstract class)

class IChip {
    public:
    virtual bool test() noexcept = 0;
};

IMemory.hpp (Abstract with some functions common to all Memories, read/write for illustration)

#include "IChip.hpp"

template <typename TAddr, typename TWord>
class IMemory: public IChip {
    protected:
    ...

    public:
    virtual TWord read(const TAddr addr) const noexcept = 0;
    virtual void write(const TAddr addr, const TWord data) const noexcept = 0;
    ...
    bool test() noexcept final override;
};

IMemory.cpp

#include "IMemory.hpp"

template <typename TAddr, typename TWord>
bool IMemory<TAddr, TWord>::test() noexcept {
    std::cout << "IMemory:test()" << std::endl;
    ...
    return true;
}

// Explicit instantiation. (Actually resides in a separate file IMemory.impl.cpp, but included here for clarity)
template class IMemory<uint16_t, uint8_t>;

HM62256.hpp (again read/write for illustration)

#include "IMemory.hpp"

class HM62256: public IMemory<uint16_t, uint8_t> {
    private:
    ...

    public:
    ...
    uint8_t read(uint16_t addr) const noexcept final override;
    void write(uint16_t addr, uint8_t data) const noexcept final override;
    ...
};

HM62256.cpp

#include "HM62256.hpp"

uint8_t HM62256::read(uint16_t addr) const noexcept {
    uint8_t result = 0;
    ...
    return result;
}

void HM62256::write(uint16_t addr, uint8_t data) const noexcept {
    ...
}

main.cpp

int main(int argc, char *argv[]) {
    const uint8_t ADDR_PINS[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18};
    const uint8_t DATA_PINS[] = {19, 20, 21, 22, 23, 24, 25, 26};
    HM62256 *device = new HM62256(ADDR_PINS, DATA_PINS, 2, 3, 27);

    device->test();    // (1)

    delete device;
}

My primary issue is as follows:

  • This code compiles with no issues (not even warnings).
  • My expectation is that device->test() at (1) will execute the test() method that HM62256 inherited from IMemory. Thus it should print IMemory:test() to the console. It does not. I set breakpoints on said test() method, but the debugger does not hit them. Instead, the execution simply goes through, nothing is printed to the console. I set a breakpoint on delete device; and it does hit it.

My secondary issue is, since the code compiles fine, that function call at (1) must call something, right? WHAT does it call?

Additional info:

  • Compiler: GCCC 8.3.0 targetting arm-linux and C++20
  • Using CMake
S. Saad
  • 336
  • 2
  • 9
  • 1
    Does this answer your question? [Why can templates only be implemented in the header file?](https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) The code in `IMemory.cpp` needs to be in the header file. – Richard Critten Nov 07 '21 at 17:45
  • 1
    [Cannot reproduce](https://godbolt.org/z/9TPxfWdnr). Please post a [mcve]. – Quimby Nov 07 '21 at 17:47
  • 2
    @RichardCritten There is an explicit instantiation in IMemory.cpp, so the code is fine at least from that standpoint. – Quimby Nov 07 '21 at 17:47
  • @RichardCritten Thank you for your comment, but no, not really. Notice the explicit instantiations in IMemory.cpp, as pointed to in this answer of that same thread https://stackoverflow.com/a/495056/5006083. – S. Saad Nov 07 '21 at 17:48
  • 2
    Use the debugger to _step into_ the call to `test` and see where you end up. – 1201ProgramAlarm Nov 07 '21 at 17:50
  • @Quimby weird. On that same link you posted, when I switch to ARM gcc there is no output, as in it behaves like I decribed. The x86 gcc works fine. – S. Saad Nov 07 '21 at 17:53
  • 1
    @S.Saad Because godbolt cannot execute ARM code, see the Output tab option above the assembly for "Execute the code". The code there is correct, it must work on ARM too. Your issue is somewhere else. Maybe try to recompile everything from scratch? – Quimby Nov 07 '21 at 17:53
  • @1201ProgramAlarm I just did that. So it jumps into a random line in the body of a random function (on IMemory, that is). I set breakpoints at the entry of said function but they are not hit. I suspect when the `device->test()` is called, the function pointer for `test` is just pointing to somewhere random in the memory. – S. Saad Nov 07 '21 at 17:58
  • 1
    @Quimby ah okay. Well I'm not sure what to do, the minimal working example is really what I posted. Thank you for the input! – S. Saad Nov 07 '21 at 17:59
  • 1
    @S.Saad If you run my code on your machine, does it work? You could try `-fsanitize=address` and other sanitizers, to see whether something isn't corrupting the vtable. But that would be pretty rare. Unfortunately it's not a mcve, you would have to do the tedious thing and create an example from scratch. The issue is obviously somewhere in your setup or the dots. I can only say that what you have showed is valid C++ and should work. – Quimby Nov 07 '21 at 18:03
  • @Quimby and indeed you were right. See my answer for details. Your help was very much appreciated, thank you. – S. Saad Nov 07 '21 at 18:54

1 Answers1

1

So, as @Quimby suggested in the comments, my issue was somewhere else in my code.

How I caught the issue:

  • Enabled some sanitization flags for the compiler and linker. This made the compiler complain about a few things that I had wrong, some of which were probably the source of my issues
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address -static-libasan)

What was the issue in my case:

I frankly do not know. Most probably, something was corrupting the vtable. I have a few suspects though:

  • I was incrementing a const variable inside a member function in IMemory, for which the compiler did not complain before adding the sanitization flags.
  • I was making use of the formatting library from C++20. Again the compiler did not complain before adding the sanitization flags, but after complained that it could not find that include. This leads me to believe that either the toolchain itself is broken OR my install of the toolchain is broken.
  • I had a couple of functions in IMemory with default arguments both in the declaration and the definition. After adding the sanitization flags, the compiler complained about that. The fix is to put the default value for the argument only in the declaration.

After fixing these three, the compiler happily compiled the code and the expected results showed up. Note that I had to statically link libasan for the sanitization flags to work.

S. Saad
  • 336
  • 2
  • 9
  • 1
    Glad to see your issue resolved. Weirdly enough, only the first is an actual issue, but it should be a compiler error unless you casted the const away. GCC does not support `std::format` yet, not sure where did you get it? The arguments again should be a compiler error, not runtime error. Weird. Anyway I can seriously recommend using the sanitizers for all debug builds, really invaluable tools. `undefined`, `address`, `leak` , `thread` is amazing, and `memory` basically replaces Valgrind if you have clang. – Quimby Nov 07 '21 at 21:20
  • 1
    @Quimby Thank you and duly noted. I kid you not, gcc happily compiled my flawed code before, no questions asked. Only until I added the sanitizers did it start shouting at me. I have no idea what's going on, like you said, some of those issues should be compile-time errors, no idea why gcc didn't catch them. Also, for format, It actually did compile AND did output the correct formatting before. I'm on a on a default rasbpian image, so I'm thinking maybe they have the [{fmt}](https://github.com/fmtlib/fmt) library installed by default. Otherwise, I have no idea what is going on. – S. Saad Nov 07 '21 at 21:27