繁体   English   中英

跨 DLL 边界的严格混叠

[英]Strict aliasing accross DLL boundary

我一直在查看 C++ 严格的别名规则,这让我想到了我之前工作中的一些代码。 我相信说代码违反了严格的别名规则,但很好奇为什么我们没有遇到任何问题或编译器警告。 我们使用 core.DLL 来接收传递给服务器应用程序的网络消息。 一个(非常)简化的例子:

#include <iostream>
#include <cstring>

using namespace std;

// These enums/structs lived in a shared .h file consumed by the DLL and server application
enum NetworkMessageId : int
{
    NETWORK_MESSAGE_LOGIN
    // ...
};

struct NetworkMessageBase
{
    NetworkMessageId type;
    size_t size;
};

struct LoginNetworkMessage : NetworkMessageBase
{
    static constexpr size_t MaxUsernameLength = 25;
    static constexpr size_t MaxPasswordLength = 50;
    
    char username[MaxUsernameLength];
    char password[MaxUsernameLength];
};

// This buffer and function was created/exported by the DLL
char* receiveBuffer = new char[sizeof(LoginNetworkMessage)];

NetworkMessageBase* receiveNetworkMessage()
{
    // Simulate receiving data from network, actual production code provided additional safety checks 
    LoginNetworkMessage msg;
    msg.type = NETWORK_MESSAGE_LOGIN;
    msg.size = sizeof(msg);
    
    strcpy(msg.username, "username1");
    strcpy(msg.password, "qwerty");
    
    memcpy(receiveBuffer, &msg, sizeof(msg));
    
    return (NetworkMessageBase*)&receiveBuffer[0]; // I believe this line invokes undefined behavior (strict aliasing)
}


// Pretend main is the server application
int main()
{
    NetworkMessageBase* msg = receiveNetworkMessage();
    switch (msg->type)
    {
    case NETWORK_MESSAGE_LOGIN:
        {
            LoginNetworkMessage* loginMsg = (LoginNetworkMessage*)msg;
            cout << "Username: " << loginMsg->username << " Password: " << loginMsg->password << endl;
        }
        break;
    }
    
    delete [] receiveBuffer; // A cleanup function defined in the DLL actually did this

    return 0;
}

据我了解, receiveNetworkMessage()调用未定义的行为。 我读过严格的别名 UB 通常与编译器优化/假设有关。 我认为这些优化与这种情况无关,因为.DLL 和服务器应用程序是分开编译的。 那是对的吗?

最后,客户端应用程序还共享了提供的 example.h,它用于创建LoginNetworkMessage ,该消息被逐字节流式传输到服务器。 这是便携的吗? 撇开包装/字节顺序问题不谈,我相信这不是因为LoginNetworkMessage的布局是非标准的,所以成员排序可能会有所不同。

据我了解,receiveNetworkMessage() 调用未定义的行为

正确的。

LoginNetworkMessage 逐字节流式传输到服务器。 这是便携的吗?

不,依赖二进制兼容性的网络通信不可移植。

除了打包/字节序问题,我相信这不是因为 LoginNetworkMessage 的布局是非标准的,所以成员排序可能会有所不同。

即使对于非标准布局类,成员的顺序也得到保证。 一个问题(除了字节序)是用于 alignment 目的的填充的数量和位置,基本类型的大小,字节中的位数(尽管公平地说,非 8 位字节的网络连接硬件可能不是你需要支持的东西)。

我认为这些优化与这种情况无关,因为.DLL 和服务器应用程序是分开编译的。 那是对的吗?

是的。 这使得代码在实践中是安全的(即使标准不知道任何 DLL 并且无论如何都认为它未定义)。

对于不同的翻译单元也是如此,除非启用了整体程序优化。

正如另一个答案所说,这里唯一的潜在问题是结构布局的可移植性。

C 标准没有尝试考虑程序的语义,其整个源代码无法同时用于 C 实现,因此对实现如何处理此类程序没有要求。 由于并非所有实现都可以支持这些概念,因此任何依赖它们的程序本质上都是“不可移植的”,并且由于标准对如何处理它们没有要求,因此它们在技术上会调用未定义的行为。

另一方面,该标准认识到未定义行为可能发生在不可移植但正确的程序中,并指定实现可以“以环境的记录方式特征”处理调用未定义行为的程序。 允许调用单独构建的代码片段的实现通常指定如何在机器级别处理对此类函数的调用,因此应该期望以记录的方式处理此类调用,而不考虑标准是否要求他们这样做.

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM