1

我有一个简单的函数,它使用 Qt 的 (5.5.1) QSharedMemory 类将字符串(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 是类的 QSharedMemory 类型的静态成员。

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

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

4

1 回答 1

2

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

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

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

    这就是所谓的不变量:无论如何,它总是正确的。这使读者能够安全地使用 C 字符串 API,而无需先检查是否存在空终止。

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

    这是写入者的后置条件:它意味着写入将在内存中放置一个完整的 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;
}
于 2017-04-13T15:04:01.820 回答