struct中的C ++可变长度数组

时间:2014-09-19 16:02:42

标签: c++ arrays struct arp

我正在编写一个用于创建,发送,接收和解释ARP数据包的程序。我有一个代表ARP头的结构,如下所示:

struct ArpHeader
{
    unsigned short hardwareType;
    unsigned short protocolType;
    unsigned char hardwareAddressLength;
    unsigned char protocolAddressLength;
    unsigned short operationCode;
    unsigned char senderHardwareAddress[6];
    unsigned char senderProtocolAddress[4];
    unsigned char targetHardwareAddress[6];
    unsigned char targetProtocolAddress[4];
};

这仅适用于长度为6的硬件地址和长度为4的协议地址。地址长度也在标题中给出,因此要正确,结构必须如下所示:

struct ArpHeader
{
    unsigned short hardwareType;
    unsigned short protocolType;
    unsigned char hardwareAddressLength;
    unsigned char protocolAddressLength;
    unsigned short operationCode;
    unsigned char senderHardwareAddress[hardwareAddressLength];
    unsigned char senderProtocolAddress[protocolAddressLength];
    unsigned char targetHardwareAddress[hardwareAddressLength];
    unsigned char targetProtocolAddress[protocolAddressLength];
};

这显然不起作用,因为在编译时不知道地址长度。模板结构也不是一个选项,因为我想填充结构的值,然后将它从(ArpHeader *)转换为(char *)以获得可以在网络上发送的字节数组或转换从(char *)到(ArpHeader *)接收的字节数组,以便解释它。

一种解决方案是创建一个包含所有头字段作为成员变量的类,一个创建表示可以在网络上发送的ARP头的字节数组的函数,以及一个只需要一个字节数组的构造函数(在通过读取所有头字段并将它们写入成员变量来解释它。这不是一个很好的解决方案,因为它需要更多的代码。

相反,例如UDP报头的类似结构很简单,因为所有报头字段都是已知的恒定大小。我用

#pragma pack(push, 1)
#pragma pack(pop)

围绕结构声明,这样我就可以实际进行简单的C样式转换,以获得在网络上发送的字节数组。

我在这里可以使用哪种解决方案可以接近结构,或者至少不需要比结构更多的代码? 我知道结构中的最后一个字段(如果它是一个数组)不需要特定的编译时大小,我可以使用类似的东西来解决我的问题吗?只是将这4个数组的大小留空将编译,但我不知道它将如何实际起作用。从逻辑上讲,它不起作用,因为如果第一个数组的大小未知,编译器就不知道第二个数组的起始位置。

3 个答案:

答案 0 :(得分:9)

您需要一个相当低级别的东西,一个ARP数据包,并且您正在尝试找到一种正确定义数据结构的方法,以便您可以将blob转换为该结构。相反,您可以在blob上使用接口。

struct ArpHeader {
    mutable std::vector<uint8_t> buf_;

    template <typename T>
    struct ref {
        uint8_t * const p_;
        ref (uint8_t *p) : p_(p) {}
        operator T () const { T t; memcpy(&t, p_, sizeof(t)); return t; }
        T operator = (T t) const { memcpy(p_, &t, sizeof(t)); return t; }
    };

    template <typename T>
    ref<T> get (size_t offset) const {
        if (offset + sizeof(T) > buf_.size()) throw SOMETHING;
        return ref<T>(&buf_[0] + offset);
    }

    ref<uint16_t> hwType() const { return get<uint16_t>(0); }
    ref<uint16_t> protType () const { return get<uint16_t>(2); }
    ref<uint8_t> hwAddrLen () const { return get<uint8_t>(4); }
    ref<uint8_t> protAddrLen () const { return get<uint8_t>(5); }
    ref<uint16_t> opCode () const { return get<uint16_t>(6); }

    uint8_t *senderHwAddr () const { return &buf_[0] + 8; }
    uint8_t *senderProtAddr () const { return senderHwAddr() + hwAddrLen(); }
    uint8_t *targetHwAddr () const { return senderProtAddr() + protAddrLen(); }
    uint8_t *targetProtAddr () const { return targetHwAddr() + hwAddrLen(); }
};

如果您需要const正确,请删除mutable,创建const_ref,然后将访问者复制到非const版本中,并制作const }版本返回const_refconst uint8_t *

答案 1 :(得分:3)

简答:你不能在C ++中拥有可变大小的类型。

C ++中的每个类型在编译期间都必须具有已知(且稳定)的大小。 IE运算符sizeof()必须给出一致的答案。注意,您可以使用堆来保存包含可变数据量的类型(例如:std::vector<int>),但实际对象的大小始终是常量。

因此,您永远不能生成一个类型声明,您将投射并获得神奇调整的字段。这深入到基本对象布局 - 每个成员(aka字段)必须具有已知(和稳定)的偏移。

通常,通过编写(或生成)解析输入数据并初始化对象数据的成员函数来解决问题。这基本上是古老的数据序列化问题,在过去30多年中已经无数次地解决了。

这是一个基本解决方案的模型:

class packet { 
public:
    // simple things
    uint16_t hardware_type() const;

    // variable-sized things
    size_t sender_address_len() const;
    bool copy_sender_address_out(char *dest, size_t dest_size) const;

    // initialization
    bool parse_in(const char *src, size_t len);

private:    
    uint16_t hardware_type_;    
    std::vector<char> sender_address_;
};

注意:

  • 上面的代码显示了可以让您执行以下操作的基本结构:

    packet p;
    if (!p.parse_in(input, sz))
        return false;
    
  • 通过RAII做同样事情的现代方式看起来像这样:

    if (!packet::validate(input, sz))
        return false;
    
    packet p = packet::parse_in(input, sz);  // static function 
                                             // returns an instance or throws
    

答案 2 :(得分:2)

如果您希望保持对数据的简单访问和数据本身public,那么可以在不改变访问数据的方式的情况下实现您的需求。首先,您可以使用std::string而不是char数组来存储地址:

#include <string>
using namespace std; // using this to shorten notation. Preferably put 'std::'
                     // everywhere you need it instead.
struct ArpHeader
{
    unsigned char hardwareAddressLength;
    unsigned char protocolAddressLength;

    string senderHardwareAddress;
    string senderProtocolAddress;
    string targetHardwareAddress;
    string targetProtocolAddress;
};

然后,您可以重载转化运算符operator const char*()和构造函数arpHeader(const char*)(当然也包括operator=(const char*)),以便保持当前的发送/接收功能正常工作,如果这就是你所需要的。

一个简化的转换运算符(跳过一些字段,使它不那么复杂,但你应该没有问题将它们添加回来),看起来像这样:

operator const char*(){
    char* myRepresentation;
    unsigned char mySize
            = 2+ senderHardwareAddress.length()
            + senderProtocolAddress.length()
            + targetHardwareAddress.length()
            + targetProtocolAddress.length();

    // We need to store the size, since it varies
    myRepresentation = new char[mySize+1];
    myRepresentation[0] = mySize;
    myRepresentation[1] = hardwareAddressLength;
    myRepresentation[2] = protocolAddressLength;

    unsigned int offset = 3; // just to shorten notation
    memcpy(myRepresentation+offset, senderHardwareAddress.c_str(), senderHardwareAddress.size());
    offset += senderHardwareAddress.size();
    memcpy(myRepresentation+offset, senderProtocolAddress.c_str(), senderProtocolAddress.size());
    offset += senderProtocolAddress.size();
    memcpy(myRepresentation+offset, targetHardwareAddress.c_str(), targetHardwareAddress.size());
    offset += targetHardwareAddress.size();
    memcpy(myRepresentation+offset, targetProtocolAddress.c_str(), targetProtocolAddress.size());

    return myRepresentation;
}

虽然构造函数可以这样定义:

ArpHeader& operator=(const char* buffer){

    hardwareAddressLength = buffer[1];
    protocolAddressLength = buffer[2];

    unsigned int offset = 3; // just to shorten notation
    senderHardwareAddress = string(buffer+offset, hardwareAddressLength);
    offset += hardwareAddressLength;
    senderProtocolAddress = string(buffer+offset, protocolAddressLength);
    offset += protocolAddressLength;
    targetHardwareAddress = string(buffer+offset, hardwareAddressLength);
    offset += hardwareAddressLength;
    targetProtocolAddress = string(buffer+offset, protocolAddressLength);

    return *this;
}
ArpHeader(const char* buffer){
    *this = buffer; // Re-using the operator=
}

然后使用你的课程就像:

ArpHeader h1, h2;
h1.hardwareAddressLength = 3;
h1.protocolAddressLength = 10;
h1.senderHardwareAddress = "foo";
h1.senderProtocolAddress = "something1";
h1.targetHardwareAddress = "bar";
h1.targetProtocolAddress = "something2";

cout << h1.senderHardwareAddress << ", " << h1.senderProtocolAddress
<< " => " << h1.targetHardwareAddress << ", " << h1.targetProtocolAddress << endl;

const char* gottaSendThisSomewhere = h1;
h2 = gottaSendThisSomewhere;

cout << h2.senderHardwareAddress << ", " << h2.senderProtocolAddress
<< " => " << h2.targetHardwareAddress << ", " << h2.targetProtocolAddress << endl;

delete[] gottaSendThisSomewhere;

哪个应该为您提供所需的实用程序,并保持您的代码正常运行而不会改变课堂外的任何内容。

但是请注意,如果你愿意稍微更改其余的代码(在这里谈论你已经写过的那个,在课堂的外面), jxh 的答案应该是工作速度与此一样快,在内侧更优雅。