【问题标题】:C++ code apparently executing out of sequenceC++ 代码显然是乱序执行的
【发布时间】:2021-12-20 23:18:40
【问题描述】:
  • 代码

这是一个使用 WiringPi 在 Raspberry Pi 上的项目。我有以下三个模板类的成员函数,以及read()write() 的纯虚函数。然后这个基类被提供read()write() 函数的更专业的类子类化(示例如下所示):

// IChip.hpp (Root abstract class)
class IChip {
    public:
    virtual bool test() noexcept = 0;
};
// End IChip.hpp

// IMemory.hpp (class of interest to the question)
class IMemory: public IChip {
    protected:
    ...
    TAddr m_wordCount;
    TWord m_dataMax;
    // ctor and dtor, and more member fields

    public:
    virtual TWord read(const TAddr addr) const noexcept = 0;
    virtual void write(const TAddr addr, const TWord data) const noexcept = 0;

    // accessors and whatnot ...

    bool march(bool keepGoing = false) noexcept;
    bool checkerboard(bool keepGoing = false) noexcept;

    bool test() noexcept final override;
};
// End IMemory.hpp


// IMemory.cpp
template <typename TAddr, typename TWord>
bool IMemory<TAddr, TWord>::march(bool keepGoing) noexcept {
    bool result = true;
    TAddr i;
    TWord r;
    const uint64_t totalIter = (m_wordCount * 6) - 1;
    uint64_t counter = 0;

    std::cout << "Starting MARCH test." << std::endl;

    for (i = 0; i < m_wordCount; i++) {
        this->write(i, 0);
        std::cout << '\r' << counter << " / " << totalIter << std::flush;
        counter++;
    }

    for (i = 0; i < m_wordCount; i++) {
        r = this->read(i);
        if (r != 0) {
            result = false;
            if (!keepGoing)
                return result;
        }
        this->write(i, m_dataMax);
        std::cout << '\r' << counter << " / " << totalIter << std::flush;
        counter++;
    }

    // 4 more similar loops

    std::cout << std::endl;

    std::cout << "MARCH test done." << std::endl;

    return result;
}

template <typename TAddr, typename TWord>
bool IMemory<TAddr, TWord>::checkerboard(bool keepGoing) noexcept {
    bool result = true;
    TAddr i;
    TWord r;
    TWord curWord;
    const uint64_t totalIter = (m_wordCount * 4) - 1;
    uint64_t counter = 0;

    std::cout << "Starting CHECKERBOARD test." << std::endl;

    curWord = 0;
    for (i = 0; i < m_wordCount; i++) {
        this->write(i, curWord);
        std::cout << '\r' << counter << " / " << totalIter << std::flush;
        counter++;
        curWord = curWord == 0 ? m_dataMax : 0;
    }

    curWord = 0;
    for (i = 0; i < m_wordCount; i++) {
        r = this->read(i);
        if (r != curWord) {
            result = false;
            if (!keepGoing)
                return result;
        }
        std::cout << '\r' << counter << " / " << totalIter << std::flush;
        counter++;
        curWord = curWord == 0 ? m_dataMax : 0;
    }

    // 2 more similar loops ...

    std::cout << std::endl;
    std::cout << "CHECKERBOARD test done." << std::endl;

    return result;
}

template <typename TAddr, typename TWord>
bool IMemory<TAddr, TWord>::test() noexcept {
    bool march_result = this->march();
    bool checkerboard_result = this->checkerboard();
    bool result = march_result && checkerboard_result;

    std::cout << "MARCH: " << (march_result ? "Passed" : "Failed") << std::endl;
    std::cout << "CHECKERBOARD: " << (checkerboard_result ? "Passed" : "Failed") << std::endl;
    return result;
}

// Explicit instantiation
template class IMemory<uint16_t, uint8_t>;
// End IMemory.cpp


// Sample read() and write() from HM62256, a subclass of IMemory<uint16_t, uint8_t>
// These really just bitbang onto / read data from pins with appropriate timings for each chip.
// m_data and m_address are instances of a Bus class, that is just a wrapper around an array of pins, provides bit-banging and reading functionality.

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

    m_data->setMode(INPUT);
    m_address->write(addr);
    digitalWrite(m_CSPin, LOW);
    digitalWrite(m_OEPin, LOW);
    delayMicroseconds(1);
    result = m_data->read();
    digitalWrite(m_OEPin, HIGH);
    digitalWrite(m_CSPin, HIGH);
    delayMicroseconds(1);
    return result;
}

void HM62256::write(uint16_t addr, uint8_t data) const noexcept {
    digitalWrite(m_OEPin, HIGH);
    delayMicroseconds(1);

    m_address->write(addr);
    delayMicroseconds(1);

    m_data->setMode(OUTPUT);
    m_data->write(data);

    digitalWrite(m_CSPin, LOW);
    digitalWrite(m_WEPin, LOW);
    delayMicroseconds(1);

    digitalWrite(m_WEPin, HIGH);
    digitalWrite(m_CSPin, HIGH);
    delayMicroseconds(1);
}

// main.cpp
void hm62256_test() {
    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};
    Chiptools::Memory::HM62256 *device = new Chiptools::Memory::HM62256(ADDR_PINS, DATA_PINS, 2, 3, 27);

    device->setup();
    bool result = device->test();
    std::cout << "Device " << ( result ? "passed all" : "failed some") << " tests." << std::endl;
    delete device;
}

int main(int argc, char *argv[]) {
    wiringPiSetupGpio();
    hm62256_test();
}
  • 输出

现在当我运行它时,有时它工作得很好:

Starting MARCH test.
196607 / 196607
MARCH test done.
Starting CHECKERBOARD test.
131071 / 131071
CHECKERBOARD test done.
MARCH: Passed
CHECKERBOARD: Passed
Device passed all tests.

但随机我会得到这个输出:

Starting MARCH test.
67113 / 196607Starting CHECKERBOARD test.
33604 / 131071MARCH: Failed
CHECKERBOARD: Failed
Device failed some tests.
  • 工具链信息
    • gcc 8.3.0 arm-linux / C++14
    • Cmake 3.16.3
    • 没有线程。
    • 编译器和链接器标志:
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address,leak,undefined")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,leak,undefined  -static-libasan")
  • 问题和我的尝试

我有几十个筹码。所有芯片都可以在 TL866ii 编程器/测试器上正常工作。所有这些芯片都会发生这种情况。这样就排除了芯片是问题的根源。

好吧,起初我想也许我没有正确刷新 cout 流,但 AFAIK std::endl 确实刷新了输出,所以不是这样。

接下来,我设置了几个断点:(A) 就在 march() 返回之前,(B) 就在调用 checkerboard() 的位置(@ 中的第二行987654333@), (C)checkerboard() 内的第一行。

  • 当输出符合预期时,断点按 A、B、C 的顺序命中。
  • 当输出不如预期时,断点按 B、C、A 的顺序命中。

看起来正在发生的事情是,有时在 march() 仍在运行时调用 checkerboard(),导致随机 GPIO 输出,此时一个或两个测试失败。

虽然我正在寻找解决方案,但我更感兴趣的是了解正在发生的事情。我会认为,由于我的代码没有使用多线程,并且根据我对 C++ 标准的理解,语句会在执行下一条语句之前一个接一个地执行到完成。我知道一些编译器实现会重新排序语句以进行优化,但 AFAIK 它不应该影响我的代码的语义。我可能是错的,因为这些东西超出了我的想象。

【问题讨论】:

  • 什么决定了resultmarch()checkerboard() 函数中是真还是假?
  • @404NotFound 感谢您的回复。 result 在省略的循环中设置。我为每个函数添加了一个循环。查看我的编辑。
  • 因此,如果march 测试失败,无论出于何种原因,它都会提前返回,不会打印MARCH test done,也不会遇到断点A(大概是您在return 处设置的函数的结束,没有到达)。然后B和C被正常击中。如果checkerboard 也失败了checkerboard 也会返回而不打印CHECKERBOARD test done。到目前为止还没有神奇的重新排序。那么,唯一的谜团就是 A 是如何被击中的。你确定它真的做到了吗?稍后你会在节目中再次致电march 吗?
  • @NateEldredge 不,没有其他对 march 的调用,这两个函数只调用一个。我很确定这不是 'march()' 失败的情况,因为如果您仔细查看输出(当它出错时),您会看到它仍然打印 "MARCH test done",但文本与checkerboard() 的输出。此外,我从未说过有神奇的重新排序。我说语句显然是乱序执行的。
  • @NateEldredge,实际上你是对的,它不会打印“MARCH test done”。感谢您指出这一点。

标签: c++ raspberry-pi templating wiringpi


【解决方案1】:

这可能不是一个答案,但评论太长了。

A 是否位于“March test done”行之后的 return 语句中?

我基于此输出创建以下 cmets:

Starting MARCH test.
67113 / 196607Starting CHECKERBOARD test.
33604 / 131071MARCH: Failed
CHECKERBOARD: Failed
Device failed some tests.

似乎正在发生的事情是您的 MARCH 测试在第三个循环中失败,因此提前返回(在循环内)。然后,您的棋盘测试在第二个循环中失败,并且也提前返回。如果A 位于我提到的位置,那么我认为该断点被命中只是运气或编译器怪癖。

也就是说,从逻辑上讲,我不希望发生故障时断点A会被击中,只针对BC。我认为A 最后被击中可能取决于它的程序是如何编译的,也许还有一些奇怪的优化。或者也许调试器在程序集中放置断点;它可能只是在最终指令上,无论如何都会被调用。尝试将断点放在std::cout 行上的return 之前,看看它是否仍然被命中。

为了扩展您的评论,这就是我在问题输出中看到的内容:

Starting MARCH test.
67113 / 196607 [march() returns early] [checkerboard() starts] Starting CHECKERBOARD test.
33604 / 131071 [checkerboard() returns early] [test() reports results] MARCH: Failed
CHECKERBOARD: Failed
Device failed some tests.

总而言之,我认为如果您更改返回行,输出将符合您的期望:

if (!keepGoing) 
    return result; 

到这样的事情:

if (!keepGoing) {
    std::cout << std::endl;
    std::cout << "MARCH test failed." << std::endl;
    return result;
}

我希望产生这样的输出:

Starting MARCH test.
67113 / 196607
MARCH test failed
Starting CHECKERBOARD test.
33604 / 131071
CHECKERBOARD test failed
MARCH: Failed
CHECKERBOARD: Failed
Device failed some tests.

【讨论】:

  • 确实,现在这很有意义。新手误区。我添加了一些文本输出,你知道吗,march() 确实失败了。至于断点,我正在使用 vscode 通过 ssh 进行远程开发,而且我一直在用它们切换不合适的地方度过一段糟糕的时光。不确定是 gdb / vscode / 还是消毒剂弄乱了断点。谢谢。
猜你喜欢
  • 2019-01-09
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
  • 2014-08-23
  • 2011-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多