2014-09-19 22 views
5

Sto scrivendo un programma per creare, inviare, ricevere e interpretare i pacchetti ARP. Ho una struttura che rappresenta l'intestazione ARP simili:Array di lunghezza variabile C++ nella struttura

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]; 
}; 

Questo funziona solo per gli indirizzi hardware con lunghezza 6 e protocollo indirizzi con lunghezza 4. L'indirizzo lunghezze sono indicate nel intestazione, così sia corretta la la struttura dovrebbe essere simile a questa:

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]; 
}; 

questo ovviamente non funzionerà in quanto le lunghezze di indirizzo non sono noti al momento della compilazione. Le strutture di template non sono un'opzione neanche perché vorrei inserire i valori per la struttura e poi lanciarli da (ArpHeader *) a (char *) per ottenere un array di byte che può essere inviato sulla rete o sul cast una matrice di byte ricevuta da (char *) a (ArpHeader *) per interpretarla.

Una soluzione sarebbe quella di creare una classe con tutti i campi di intestazione come variabili utente, una funzione per creare una matrice di byte che rappresenta l'intestazione ARP che può essere inviato sulla rete e un costruttore che vorrebbe solo un array di byte (ricevuto sulla rete) e interpretarlo leggendo tutti i campi di intestazione e scrivendoli alle variabili membro. Questa non è una buona soluzione, dal momento che richiederebbe molto più codice.

Al contrario una struttura simile per un'intestazione UDP, ad esempio, è semplice poiché tutti i campi di intestazione sono di dimensione costante nota. Io uso

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

attorno alla dichiarazione della struttura in modo che io possa effettivamente fare una semplice fusione C-stile per ottenere un array di byte da inviare sulla rete.

C'è qualche soluzione che potrei usare qui che sarebbe vicino a una struttura o almeno non richiedere molto più codice di una struttura? So che l'ultimo campo di una struttura (se si tratta di un array) non ha bisogno di una specifica dimensione di compilazione, posso usare qualcosa di simile come quello per il mio problema? Basta lasciare le dimensioni di quei 4 array vuoti per compilare, ma non ho idea di come funzionerebbe. Solo logicamente parlando non può funzionare poiché il compilatore non avrebbe idea di dove inizia il secondo array se la dimensione del primo array è sconosciuta.

+3

Se la dimensione massima dell'indirizzo è 6, non è possibile creare gli array di dimensioni [6] e quindi interpretarli di conseguenza? Questa è la soluzione più semplice se vuoi evitare un sacco di codice.Un'altra opzione sarebbe quella di utilizzare un grande array di lunghezza fissa per tutti gli indirizzi e scrivere una funzione che prepari un array di byte basato sulle lunghezze degli indirizzi –

+0

Gli array di lunghezza zero oi membri di array flessibili non sono C++ validi. – pmr

+1

perché non usare std :: string o std :: vector come membro di una struttura? E se usi una struttura, perché non gli dai la funzionalità? Dividere il codice in dati e programmi è esattamente l'opposto della programmazione orientata agli oggetti. La tua domanda suona come un fallimento del design! – Klaus

risposta

6

Si desidera una cosa abbastanza basso livello, un pacchetto ARP, e si sta cercando di trovare un modo per definire propriamente una datastructure in modo da poter lanciare il blob in quella struttura. Invece, è possibile utilizzare un'interfaccia sopra il 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(); } 
}; 

Se avete bisogno di const correttezza, si rimuove mutable, creare un const_ref, e duplicare le funzioni di accesso in non const versioni, e rendere le versioni const tornano const_ref e const uint8_t *.

3

Risposta breve: non è possibile avere tipi di dimensioni variabili in C++.

Ogni tipo in C++ deve avere una dimensione nota (e stabile) durante la compilazione. L'operatore IE sizeof() deve fornire una risposta coerente. Nota, puoi avere tipi che contengono quantità variabili di dati (es .: std::vector<int>) usando l'heap, tuttavia la dimensione dell'oggetto reale è sempre costante.

Quindi, non è mai possibile produrre una dichiarazione di tipo che si sarebbe cast e ottenere i campi magicamente regolati. Ciò entra in profondità nel layout dell'oggetto fondamentale: ogni membro (detto anche campo) deve avere un offset noto (e stabile).

In genere, il problema si risolve scrivendo (o generando) funzioni membro che analizzano i dati di input e inizializzano i dati dell'oggetto. Questo è fondamentalmente il vecchio problema di serializzazione dei dati, che è stato risolto innumerevoli volte negli ultimi 30 anni circa.

Ecco un mockup di una soluzione di base:

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_; 
}; 

Note:

  • il codice mostra la struttura di base che avrebbe permesso di effettuare le seguenti operazioni di cui sopra:

    packet p; 
    if (!p.parse_in(input, sz)) 
        return false; 
    
  • il modo moderno di fare la stessa cosa tramite RAII sarebbe il seguente:

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

Capisco come funzioni la programmazione orientata agli oggetti incluse le classi ecc. Lo scopo di questo post era quello di scoprire se c'è un modo più veloce con meno codice per scrivere un oggetto header ARP come scriverei una struttura di intestazione UDP che può essere essenzialmente lanciata su un array di byte al fine di evitare lunghe funzioni di conversione. Ma grazie comunque per chiarire questo. – PlanckMax

2

Se si desidera mantenere l'accesso alla semplice dei dati e dei dati stessi public, c'è un modo per ottenere ciò che si vuole senza cambiare il modo in cui si accede ai dati. In primo luogo, è possibile utilizzare std::string al posto degli array char per memorizzare gli indirizzi:

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

Quindi, è possibile sovraccaricare l'operatore di conversione operator const char*() e il costruttore arpHeader(const char*) (e ovviamente operator=(const char*) preferibilmente troppo), al fine di mantenere il vostro funzioni di invio/ricezione correnti funzionanti, se è ciò di cui hai bisogno.

Un operatore di conversione semplificata (saltati alcuni campi, per rendere meno complicato, ma si dovrebbe avere alcun problema ad aggiungere di nuovo), sarebbe simile a questa:

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

Mentre il costruttore può essere definito come ad esempio:

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= 
} 

Quindi, utilizzando la classe è semplice come:

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; 

Che dovrebbe offrirti l'utilità necessaria e mantenere il tuo codice funzionante senza cambiare nulla fuori dalla classe.

nota, tuttavia, che se siete disposti a cambiare il resto del codice un po '(parlando di quello che hai scritto già, ouside della classe), JXH 's risposta dovrebbe funzionare più velocemente questo è più elegante sul lato interno .

+0

Grazie, questa è una soluzione totalmente valida per il problema, ma come hai già detto la risposta di jhx è più elegante a un livello inferiore ed è in realtà una specie di cosa stavo cercando dato che richiede solo un piccolo codice. – PlanckMax

Problemi correlati