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 不能保证崩溃。通常它会在重要的演示期间崩溃。如果字符串太长而无法放入内存段,您也不能确保该字符串将以零终止,因此如果接收器试图将字符串视为以零终止,则它可能会崩溃。
这些问题可能是由于缺乏设计造成的。内存段的内容暗示了发送者和接收者之间的契约。双方必须就某事达成一致。让我们定义合约:
共享内存段的内容是一个以 null 结尾的 C 字符串。
这就是所谓的不变量:无论发生什么,它始终为真。这使 reader 能够安全地使用 C 字符串 API,而无需首先检查是否存在空终止。
太长的 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;
}
我有一个简单的函数,它使用 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 不能保证崩溃。通常它会在重要的演示期间崩溃。如果字符串太长而无法放入内存段,您也不能确保该字符串将以零终止,因此如果接收器试图将字符串视为以零终止,则它可能会崩溃。
这些问题可能是由于缺乏设计造成的。内存段的内容暗示了发送者和接收者之间的契约。双方必须就某事达成一致。让我们定义合约:
共享内存段的内容是一个以 null 结尾的 C 字符串。
这就是所谓的不变量:无论发生什么,它始终为真。这使 reader 能够安全地使用 C 字符串 API,而无需首先检查是否存在空终止。
太长的 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;
}