C++ 代码显然乱序执行
C++ code apparently executing out of sequence
- 代码
这是 Raspberry Pi 上使用 WiringPi 的项目。我有以下三个模板 class 的成员函数,以及 read()
和 write()
的纯虚函数。这个基础 class 然后由提供 read()
和 write()
功能的更专业的 class 子class 编辑(示例如下所示):
// 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()
之前returns,(B) 就在调用 checkerboard()
的地方(test()
中的第 2 行),(C) 在 checkerboard()
.[=26= 中的第 1 行]
- 当输出符合预期时,按此顺序命中断点 A、B、C。
- 当输出不符合预期时,按此顺序命中断点B,C,A。
看起来正在发生的事情是,有时 checkerboard()
被调用而 march()
仍然是 运行ning,导致随机 GPIO 输出,此时一个或两个测试失败。
虽然我正在寻找解决方案,但我更感兴趣的是了解正在发生的事情。我会认为,由于我的代码没有使用多线程,并且根据我对 C++ 标准的理解,在执行下一条语句之前,语句会一个接一个地执行到完成。我知道一些编译器实现会重新排序语句以进行优化,但 AFAIK 它不应该影响我的代码的语义。我可能错了,因为这些东西让我难以理解。
这可能不是答案,但评论太长了。
A
是否在“March test done”行之后的 return
语句中?
我基于此输出做出以下评论:
Starting MARCH test.
67113 / 196607Starting CHECKERBOARD test.
33604 / 131071MARCH: Failed
CHECKERBOARD: Failed
Device failed some tests.
似乎正在发生的事情是您的 MARCH 测试在第 3 个循环中失败,因此 returning 提前(在循环内)。然后,您的棋盘测试在第二个循环中失败,并且提前 returns。如果 A
位于我提到的位置,那么我认为命中该断点只是运气或编译器怪癖。
也就是说,从逻辑上讲,我不希望在发生故障时命中断点 A
,仅针对 B
和 C
。我认为 A
最后被击中可能是因为它的程序是如何编译的,也许还有一些奇怪的优化。或者也许调试器在程序集中放置断点的位置;它可能只是在无论如何都会被调用的最终指令上。尝试将断点放在 return
之前的 std::cout
行,看看它是否仍然命中。
为了扩展您的评论,这是我在问题输出中看到的内容:
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.
总而言之,我认为如果您更改 return 行,输出将符合您的期望:
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.
- 代码
这是 Raspberry Pi 上使用 WiringPi 的项目。我有以下三个模板 class 的成员函数,以及 read()
和 write()
的纯虚函数。这个基础 class 然后由提供 read()
和 write()
功能的更专业的 class 子class 编辑(示例如下所示):
// 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()
之前returns,(B) 就在调用 checkerboard()
的地方(test()
中的第 2 行),(C) 在 checkerboard()
.[=26= 中的第 1 行]
- 当输出符合预期时,按此顺序命中断点 A、B、C。
- 当输出不符合预期时,按此顺序命中断点B,C,A。
看起来正在发生的事情是,有时 checkerboard()
被调用而 march()
仍然是 运行ning,导致随机 GPIO 输出,此时一个或两个测试失败。
虽然我正在寻找解决方案,但我更感兴趣的是了解正在发生的事情。我会认为,由于我的代码没有使用多线程,并且根据我对 C++ 标准的理解,在执行下一条语句之前,语句会一个接一个地执行到完成。我知道一些编译器实现会重新排序语句以进行优化,但 AFAIK 它不应该影响我的代码的语义。我可能错了,因为这些东西让我难以理解。
这可能不是答案,但评论太长了。
A
是否在“March test done”行之后的 return
语句中?
我基于此输出做出以下评论:
Starting MARCH test.
67113 / 196607Starting CHECKERBOARD test.
33604 / 131071MARCH: Failed
CHECKERBOARD: Failed
Device failed some tests.
似乎正在发生的事情是您的 MARCH 测试在第 3 个循环中失败,因此 returning 提前(在循环内)。然后,您的棋盘测试在第二个循环中失败,并且提前 returns。如果 A
位于我提到的位置,那么我认为命中该断点只是运气或编译器怪癖。
也就是说,从逻辑上讲,我不希望在发生故障时命中断点 A
,仅针对 B
和 C
。我认为 A
最后被击中可能是因为它的程序是如何编译的,也许还有一些奇怪的优化。或者也许调试器在程序集中放置断点的位置;它可能只是在无论如何都会被调用的最终指令上。尝试将断点放在 return
之前的 std::cout
行,看看它是否仍然命中。
为了扩展您的评论,这是我在问题输出中看到的内容:
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.
总而言之,我认为如果您更改 return 行,输出将符合您的期望:
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.