【问题标题】:c++ O3 optimization breaks working while loopc ++ O3优化在循环中中断工作
【发布时间】:2016-05-13 11:29:52
【问题描述】:

我有这个简单的代码,可以在 pcm wav 文件中使用 fseek 逐步搜索文件中的“数据”:

  FILE * waveFile;
  waveFile = fopen ( this->fileLocation.c_str ( ), "rb" );

  // ... some other code here between, then ... //

  int seekTo = 0;
  bool found = false;
  char data[4];

  rewind ( waveFile );
  while ( !found && ( fseek ( waveFile, seekTo, SEEK_SET ) == 0 )) {
    fread ( data, sizeof ( data ), 1, waveFile );
    if (( std::strcmp ( data, "data" ) == 0 ) || ( std::strcmp ( data, "Data" ) == 0 ) || ( std::strcmp ( data, "DATA" ) == 0 )) {
      found = true;
      fread ( &waveHeader->DATA_SIZE, sizeof ( waveHeader->DATA_SIZE ), 1, waveFile );
    }
    seekTo++;
  }

代码工作正常,它在测试文件上找到数据,读取剩余的数据。由于即使是最大的文件,“数据”也接近开头,所以这段代码对我来说没问题。

但是,当我添加 cpp 标志 -O3 时,代码变得混乱,while 循环永远不会结束。

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")

我正在使用 cmake + lldb (osx, clion),如果我使用 GDB,也会发生同样的情况。

可能是什么问题,我该如何解决?

PS。我不是想改进你看到的代码,我想了解为什么编译器优化会破解这个 while 循环。

PSS。 这是空终止的工作代码:

  int seekTo = 0;
  char data[5];

  rewind ( waveFile );
  while (( fseek ( waveFile, seekTo, SEEK_SET ) == 0 )) {
    fread ( data, 4, 1, waveFile );
    data[ 4 ] = '\0';

    if (( std::strcmp ( data, "data" ) == 0 ) || ( std::strcmp ( data, "Data" ) == 0 ) || ( std::strcmp ( data, "DATA" ) == 0 )) {
      fread ( &waveHeader->DATA_SIZE, sizeof ( waveHeader->DATA_SIZE ), 1, waveFile );
      break;
    }
    seekTo += 1;
  }

【问题讨论】:

  • std::strcmp ( data, "data" ) == 0 的读数超出了data 的范围。您忘记了空终止。也许你想memcmp 长度为4
  • 您还应该检查fread是否成功,如果失败则中断循环
  • @M.M 我最初在代码中使用 size_t 检查 fread 并在某些时候返回 0。但是在我拥有的每个文件中都有“数据”,并且在找到任何东西之前就到达了文件末尾。所以我删除了调试的结束检查。
  • 如果在 -O3 出现问题,则意味着您的代码存在错误。您应该找到错误而不是进行随机更改(这很可能只是为了使错误的影响出现在其他地方)。如果你的 memset 写的越界,那么它不应该在任何优化级别,如果它没有写越界,那么大概你有一些理由写它,由你的程序逻辑决定,那不会也可以根据优化级别进行更改。
  • 您是否幸运(或不幸)取决于如何查看它,您的代码读取(可能写入)超出了数组的范围,这仅取决于该内存中发生了什么以及是否优化器删除了它检测到的任何代码块,它会越界访问等等。除了检查编译器为代码生成的汇编代码之外,您无法得到更详细的答案。

标签: c++ c++11 compiler-optimization fseek


【解决方案1】:

由于没有其他人愿意写答案...当代码在优化关闭但停止使用优化时,很可能是编译器优化揭示了一些未定义的行为。在您的情况下,该错误是:

char data[4];
...
fread ( data, sizeof ( data ), 1, waveFile );
if (( std::strcmp ( data, "data" ) == 0 ) || ( std::strcmp ( data, "Data" ) == 0 ) || ( std::strcmp ( data, "DATA" ) == 0 )) {

strcmp 用于:

按字典顺序比较两个 null 终止 字节字符串。

所以data 恰好在某个地方有一个\0,比较结果是错误的(因为data 太短了)。或者它没有,并且您将在data 的末尾向内存中的某个随机空字节读取。结果,编译器可以推断出 不可能 比较可能是正确的,并将您的代码优化为:

if (false) { ... }

然后完全删除if 语句。

也许在未优化的构建中,您碰巧总是在 data 之后立即有零内存,而 if 从未被优化掉?


一个简单的解决方法是确保data 是空终止的:

char data[5];
data[4] = '\0';
// rest as before

或者将您对strcmp 的调用替换为memcmp,提供sizeof(data) 作为附加长度参数。

【讨论】:

  • 感谢您理解基本问题 :) 我在调试中可以看到,通过优化,char[] 数组最后会得到一些额外的字符并且大小是错误的,但这不会发生在其他案子。但是,程序会继续读取该文件。这就是我所需要的。我已经有了空终止的更正版本,在 -o3 中工作正常。但我想知道为什么它在没有优化的情况下正常工作。
  • 没有优化就不能正常工作。它在开启和关闭优化的情况下都显示了未定义的行为。未定义的行为可能包括让恶魔从你的鼻子里飞出,向你的下一任老板发送辞职信但最可怕的事情可能恰好是你想做的事。这就是禁用优化后得到的结果。如果没有优化,它仍然会损坏,但碰巧可以工作,并且可能随时停止工作。
【解决方案2】:

strcmp 是一个字符串比较函数,比较字符串直到找到 NUL 字符。您正在为字符串使用 char[4],因此 NUL 字符没有空格。事实上,这是一个意外。

在您的情况下,您最好使用 memcpy 4 个字节。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 2014-07-30
    • 2017-02-28
    • 1970-01-01
    • 2011-08-31
    • 2013-05-30
    相关资源
    最近更新 更多