C++/Qt memcpy 与 QSharedMemory 崩溃

C++/Qt memcpy crash with QSharedMemory

我有一个简单的函数,它使用 Qt (5.5.1) QSharedMemory class.

将字符串(uri 链接或文件路径)发送到已经 运行 的应用程序实例

它似乎在大多数时候都能正常工作,但我从用户那里捕获了一个崩溃日志,它在 memcpy 上崩溃了。该函数如下所示:

void WindowsApp::SendData( char* uri )
{
    int size = 1024;
    if (!m_SharedMemory.create(size)) {
        qDebug() << "Unable to create shared memory segment." << m_SharedMemory.error();
        return;
    }
    m_SharedMemory.lock();
    char *to = (char*)m_SharedMemory.data();
    const char *from = uri;
    memcpy(to, from, qMin(m_SharedMemory.size(), size));
    m_SharedMemory.unlock();
    QThread::sleep(10);
}

m_SharedMemory 是 class.

的 QSharedMemory 类型静态成员

从日志中,我看到我尝试发送的字符串是一个简单的文件路径,没有特殊字符,而且不太长,只有 150 个字符。

可能有什么问题,但我无法用类似的参数重现它?

代码有未定义的行为,因为您正在读取超过源字符串的末尾:您总是读取 1024 个字节,即使源字符串是,例如5 个字节长。正如您所指出的,UB 不能保证崩溃。通常它会在重要的演示期间崩溃。如果字符串太长而无法放入内存段,您也不能确保该字符串将以零终止,因此如果接收器试图将字符串视为以零终止,则它可能会崩溃。

这些问题可能是由于缺乏设计造成的。内存段的内容暗示了发送者和接收者之间的契约。双方必须就某事达成一致。让我们定义合约:

  1. 共享内存段的内容是一个以 null 结尾的 C 字符串。

    这就是所谓的不变量:无论发生什么,它始终为真。这使 reader 能够安全地使用 C 字符串 API,而无需首先检查是否存在空终止。

  2. 太长的 uri 被替换为空字符串。

    这对作者来说是一个post条件:它意味着写作将在内存中放置一个完整的 URI,或者一个空字符串。

修复方法如下:

bool WindowsApp::WriteShared(const char * src, int length) {
   if (m_SharedMemory.lock()) {
      auto const dst = static_cast<char*>(m_SharedMemory.data());
      Q_ASSERT(dst);
      memcpy(dst, src, length);
      m_SharedMemory.unlock();
      return true;
   }
   return false;
}

bool WindowsApp::SendData(const char* uri)
{
   Q_ASSERT(uri);
   if (!m_SharedMemory.create(1024)) {
      qWarning() << "Unable to create shared memory segment." << m_SharedMemory.error();
      return false;
   }
   int const uriLength = strlen(uri) + 1;
   if (uriLength > m_SharedMemory.size()) {
      qWarning() << "The uri is too long.";
      if (! WriteShared("", 1))
         qWarning() << "Can't clear the memory.";
      return false;
   }
   if (! WriteShared(uri, uriLength)) {
      qWarning() << "Can't lock the shared memory segment.";
      return false;
   }
   QThread::sleep(10);
   return true;
}