2010-12-23 5 views
5

in C (unter Verwendung von gcc) I kann eine variable Länge struct erklären, wie unten:alloc ein struct mit Null-Länge-Array mit neuen

typedef struct ProtocolFrame 
{ 
    uint8_t  op; 
    uint32_t  address; 
    uint16_t  size; 
    uint8_t  payload[0]; 
} ProtocolFrame; 

dann kann ich verschiedene Rahmen Alloc:

ProtocolFrame *frA; 
ProtocolFrame *frB; 

frA = malloc(sizeof(ProtocolFrame) + 50); 
frB = malloc(sizeof(ProtocolFrame)); 

In Dieses Beispiel hat ein Nutzlastfeld so groß wie 50 Bytes, und frB hat keine Nutzlast

Kann ich das gleiche in C++ mit dem neuen Operator tun?

Antwort

9
template<size_t s> 
struct ProtocolFrame 
{ 
    uint8_t  op; 
    uint32_t  address; 
    uint16_t  size; 
    uint8_t  payload[s]; 
} ProtocolFrame; 

// specialize for no payload 
template<> 
struct ProtocolFrame<0> 
{ 
    uint8_t  op; 
    uint32_t  address; 
    uint16_t  size; 
} ProtocolFrame; 

ProtocolFrame<50> *frA = new ProtocolFrame<50>; 
ProtocolFrame<0> *frB = new ProtocolFrame<0>; 

Um zu entscheiden, was die Größe zur Laufzeit ist Sie placement-neuer Betreiber in Zusammenarbeit mit std::malloc verwenden:

void *buffer = std::malloc(sizeof(ProtocolFrame)+50); 
ProtocolFrame *frA = new (buffer) ProtocolFrame; 

Sie können lesen Sie auch auf der codeproject.com this article, die die volle Probe enthalten .

+2

Das ist nicht das Gleiche, denn mit malloc hat er eine dynamisch erweiterte Erweiterung, die statisch groß ist. – Puppy

+0

Sie brauchen nicht unbedingt 'std :: malloc'. Sie könnten 'new char [sizeof (ProtocolFrame) + 50] schreiben;' aber ich weiß nicht, ob das die Ausrichtung garantiert. –

+2

@Chris: ':: operator new (sizeof (ProtocolFrame) +50)' garantiert die Ausrichtung. –

0

Auch in C-Stil, ich denke, das ist barbarisch Code.

und sizeof() Ihre Struktur wird immer noch die gleiche Größe zurückgeben.

Warum behandelst du payload nicht als normales dynamisches Array?

EDIT: ich Kirill V. Lyadvinsky ‚s Antwort

EDIT2 mag: @ Chris oh ich sehe, gibt es 2 Anrufe zu malloc ... ja es weniger effizient

inline ProtocolFrame * createProtocolFrame(int i) 
{ 
    ProtocolFrame * pProto = malloc(sizeof(ProtocolFrame)); 
    pProto->payload = malloc(i * sizeof(uint8_t)); 
} 
+0

Es mag barbarisch sein, aber die Effizienz kann es wert sein, und die API-Funktionen, die Sie schreiben, um diese Details zu abstrahieren (Sie schreiben diese, oder?), Werden nicht viel anders sein als der dynamische Ansatz mit zwei Allokationen . –

+0

Mein Punkt ist, dass der Benutzer 'createProtocolFrame' aufruft und sich nicht darum kümmert, welche Art von barbarischen Interna darunter stattfinden. Wenn die Effizienz kein Problem ist, würde sich das OP nicht mit einem flexiblen Array-Mitglied befassen und würde Ihren Ansatz verwenden. Manchmal ist Effizienz jedoch ein Problem. –

+0

Vielleicht lohnt es sich. Wenn der Protokollrahmen für jede gesendete und empfangene Nachricht erstellt wird, wird er oft genug für eine schnellere Zuordnung erstellt, Sie haben Recht. Aber es ist nicht sicher, bis ich seinen Code gesehen habe ;-) –

1

Typischerweise ist, Sie würden std :: vector verwenden.

class ProtocolFrame { 
    // Invokes undefined behaviour if stuff is not POD. 
    struct stuff { 
     stuff(uint8_t lop, uint32_t laddress, uint16_t lsize) 
      : op(lop), address(laddress), size(lsize) { 
     } 
     uint8_t op; 
     uint32_t address; 
     uint16_t size; 
    }; 
    std::vector<uint8_t> payload; 
public: 
    ProtocolFrame(int payloadsize, uint8_t op, uint32_t address, uint16_t size) 
     : payload(size + sizeof(stuff)) { 
     new (&payload[0]) stuff(op, address, size); 
    } 
    // other methods here 
    uint32_t GetAddress() { 
     return ((stuff*)&payload[0])->address; 
    } 
    uint16_t GetSize() { 
     return ((stuff*)&payload[0])->size; 
    } 
    uint8_t GetOp() { 
     return ((stuff*)&payload[0])->op; 
    } 
    std::vector<uint8_t>::iterator begin() { 
     return payload.begin() + sizeof(stuff); 
    } 
    std::vector<uint8_t>::iterator end() { 
     return payload.end(); 
    } 
}; 

Diese Art von Code ist jedoch ziemlich schrecklich.

+1

'vector' weist Speicher aus dem Heap zu, offensichtlich möchte OP einen kontinuierlichen Rahmen. –

+0

@ Krill: Kann gemacht werden. Ich muss nur meine Antwort ein wenig bearbeiten. – Puppy

2

Verwenden Platzierung neuer

char *buf = new char[sizeof(ProtocolFrame) + 50]; //pre-allocated buffer 
ProtocolFrame *frA = new (buf) ProtocolFrame; //placement new 

// STUFF 

frA->~ProtocolFrame(); 
delete [] buf; 

wenn Sie fra löschen es wird

EDIT ProtocolFrame destructor und frei buf Amtsruf: Ich habe gelesen, dass Sie die destructor löschen nicht nennen sollte, sondern direkt. Ich denke, es könnte ein Compiler-spezifisches Verhalten sein. Ich habe das Placement nicht viel benutzt, aber als ich es getan habe, habe ich delete angerufen und es hat mit MSVC++ funktioniert. Der korrekte Standardweg scheint also frA-> ~ ProtocolFrame() zu sein; und dann buf löschen; Das sieht schrecklich aus! Ich schlage vor, Sie möchten vielleicht darüber nachlesen.

+0

Sollte Platzierung außerhalb der Grenzen einer Klasse nicht verwenden. Andernfalls können Sie den Speicher nicht ordnungsgemäß verwalten, wenn Ausnahmen auftreten. –

1

In C++ haben Sie Klassen. Sollte nicht der Konstruktor von ProtocolFrame einen Parameter bekommen, wie viel payload Sie wollen?

struct ProtocolFrame { 
    uint8_t  op; 
    uint32_t  address; 
    uint16_t  size; 
    uint8_t  *payload; 

    public: 
     ProtocolFrame (int size) { 
      payload = new uint8_t [size]; 
     } 

     ~ProtocolFrame() { 
      delete [] payload; 
     } 
} 
+0

Sie müssen entweder den Kopierkonstruktor/den Zuweisungsoperator hinzufügen oder sie deaktivieren, indem Sie den privaten Operator erstellen. Alternativ können Sie das Array nicht verwenden, sondern einen Vektor verwenden. –

+0

Eine 'struct' hat bereits Methoden als private. – Jaywalker

+1

eine 'class' defaults private, eine 'struct' defaults public für Kompatibilität mit existierendem C-Code (der kein Konzept von privaten oder Klassen hat). –

1

Sie wissen nicht, ob dies immer noch von Interesse ist, aber Sie können operator new dies zu tun, überlasten. Dies funktioniert mit G ++ 4.0.1 (ich weiß nicht, wie „schön“ ist, fühlen sich frei, um sie zu bearbeiten und verbessern):

#include <cstddef> 

template <typename T> 
class test { 
    public: 
    std::size_t len; 
    T arr[1]; 

    void *operator new(std::size_t s, std::size_t a); 

    test(const T& f) { fill(f); } 
    test(); 

    private: 
    void fill(const T& f) { for(std::size_t i = 0; i < len; i++) arr[i] = f; } 
}; 

template <typename T> 
void *test<T>::operator new(std::size_t s, std::size_t a) 
{ 
    void *p = ::operator new(s + (a - 1) * sizeof(T)); 
    // this is bad and we shouldn't do this here blah blah blah 
    // but I don't know how to pass a to the ctor so this is what I did 
    ((test<T> *)p)->len = a; 
    return p; 
} 

Die Verwendung ist nicht zu schrecklich entweder:

#include <iostream> 

int main() 
{ 
    test<char> *c = new (10) test<char>('c'); 
    std::cout << c->arr[3] << std::endl; 
    delete c; 
    return 0; 
} 

Obwohl die Größe-in- Ort möglicherweise nicht möglich über new.