2014-09-19 2 views
5

Я пишу программу для создания, отправки, получения и интерпретации ARP-пакетов. У меня есть структура, представляющая ARP заголовок так:C++ массивы переменной длины в структуре

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, который может быть отправлен в сети, и конструктор, который принимает только массив байтов (полученный в сети) и интерпретировать его, читая все поля заголовка и записывая их в переменные-члены. Это нехорошее решение, так как для этого потребуется LOT больше кода.

В отличие от аналогичной структуры для UDP-заголовка, например, просто, поскольку все поля заголовка имеют известный постоянный размер. Я использую

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

вокруг объявления структуры, так что я могу на самом деле сделать простой бросок C-стиле, чтобы получить массив байтов для отправки в сети.

Есть ли какое-нибудь решение, которое я мог бы использовать здесь, которое было бы близко к структуре или, по крайней мере, не требовало бы намного большего количества кода, чем структура? Я знаю, что последнее поле в структуре (если это массив) не нуждается в определенном размере времени компиляции, могу ли я использовать что-то подобное для моей проблемы? Просто оставляя размеры этих 4 массивов пустыми, будет компилироваться, но я понятия не имею, как это будет функционировать. Просто логически это не сработает, поскольку компилятор понятия не имел, где начинается второй массив, если размер первого массива неизвестен.

+3

Если максимальный размер адреса равен 6, вы не можете сделать массивы размера [6], а затем интерпретировать их соответственно? Это самое простое решение, если вы хотите избежать большого количества кода.Другим вариантом было бы использовать один большой массив фиксированной длины для всех адресов и написать функцию, которая будет готовить массив байтов на основе длин адресов –

+0

Матрицы с нулевой длиной или гибкие члены массива недействительны C++. – pmr

+1

Почему бы не использовать std :: string или std :: vector как член структуры? И если вы используете структуру, почему вы не предоставляете им функциональность? Разделение кода на данные и программу в точности противоположно объектно-ориентированному программированию. Ваш вопрос сам по себе звучит как неудача дизайна! – Klaus

ответ

6

Вы хотите довольно низкий уровень вещи, пакет ARP, и вы пытаетесь найти способ определить правильно структуру данных, так что вы можете бросить каплю в эту структуру. Вместо этого вы можете использовать интерфейс над блобом.

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_ref и const uint8_t *.

3

Короткий ответ: Вы просто не можете иметь типы переменных в C++.

Каждый тип C++ должен иметь известный (и стабильный) размер во время компиляции. Оператор IE sizeof() должен дать последовательный ответ. Обратите внимание: вы можете иметь типы, которые содержат переменное количество данных (например: std::vector<int>), используя кучу, но размер фактического объекта всегда постоянный.

Таким образом, вы никогда не сможете создать декларацию типа, которую вы должны использовать, и получить волшебные настройки полей. Это глубоко вписывается в основную схему объекта - каждый член (aka field) должен иметь известное (и стабильное) смещение.

Обычно проблема решена путем записи (или генерации) функций-членов, которые анализируют входные данные и инициализируют данные объекта. Это в основном старая проблема сериализации данных, которая была решена бесчисленное количество раз за последние 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 
    
+0

Я понимаю, как работает объектно-ориентированное программирование, включая классы и т. Д. Цель этого сообщения состояла в том, чтобы выяснить, есть ли более быстрый способ с меньшим количеством кода для записи объекта заголовка ARP, например, я бы написал структуру заголовка UDP, которая по существу может быть передана в массив байтов, чтобы избежать длительных функций преобразования. Но благодарю вас за разъяснение. – PlanckMax

2

Если вы хотите сохранить доступ к данным простым и сами данные public, существует способ достижения желаемого результата без изменения способа доступа к данным. Во-первых, вы можете использовать std::string вместо полукокса массивов для хранения адресов:

#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; 

Который должен предложить вам нужную утилиту и поддерживать работу вашего кода, не меняя ничего из класса.

Обратите внимание, что если вы готовы немного изменить остальную часть кода (говоря здесь о том, что вы уже написали, ouside класса), Ответ jxh должен работать так же быстро, как и это, и более элегантно на внутренней стороне .

+0

Спасибо, это вполне допустимое решение проблемы, но, как вы уже упоминали, ответ jhx более изящный на более низком уровне, и на самом деле это то, что я искал, так как он требует лишь небольшого кода. – PlanckMax

Смежные вопросы