2

If I have a typedef of a struct

typedef struct 
{
char    SmType;
char    SRes;
float   SParm;
float   EParm;
WORD    Count;
char    Flags;
char    unused;
GPOINT2 Nodes[];
} GPATH2;

and it contains an uninitialized array, how can I create an instance of this type so that is will hold, say, 4 values in Nodes[]?

Edit: This belongs to an API for a program written in Assembler. I guess as long as the underlying data in memory is the same, an answer changing the struct definition would work, but not if the underlying memory is different. The Assembly Language application is not using this definition .... but .... a C program using it can create GPATH2 elements that the Assembly Language application can "read".

Can I ever resize Nodes[] once I have created an instance of GPATH2?

Note: I would have placed this with a straight C tag, but there is only a C++ tag.

5
  • Given that this needs to be in a valid state before use, and I'm guessing some parts have limited valid values (maybe char SmType has only certain valid types), why wouldn't this be a class instead of a struct? Anyone? (not much of a C++ guy myself so I'm not suggesting this as an answer) Is it because it has no behavior? Commented Nov 5, 2010 at 18:11
  • @Stephen: The 'struct' class-key declares classes, just like 'class' does. The only difference is default accessibility for bases and members. Commented Nov 5, 2010 at 18:14
  • I would bet a cookie that this is part of an interface to a library written in C, possibly with C++ interoperability issues not even considered. Commented Nov 5, 2010 at 18:58
  • You are right, this is out of an API to an application written in Assembler. Commented Nov 5, 2010 at 22:15
  • FYI, there is a straight "C" tag, which I have added for you. I left "C++" though since the answers talk a lot about C++. Commented Nov 7, 2010 at 4:24

5 Answers 5

3

You could use a bastard mix of C and C++ if you really want to:

#include <new>
#include <cstdlib>

#include "definition_of_GPATH2.h"

using namespace std;

int main(void)
{
    int i;
    /* Allocate raw memory buffer */
    void * raw_buffer = calloc(1, sizeof(GPATH2) + 4 * sizeof(GPOINT2));
    /* Initialize struct with placement-new */
    GPATH2 * path = new (raw_buffer) GPATH2;

    path->Count = 4;
    for ( i = 0 ; i < 4 ; i++ )
    {
        path->Nodes[i].x = rand();
        path->Nodes[i].y = rand();
    }

    /* Resize raw buffer */
    raw_buffer = realloc(raw_buffer, sizeof(GPATH2) + 8 * sizeof(GPOINT2));

    /* 'path' still points to the old buffer that might have been free'd 
     * by realloc, so it has to be re-initialized
     * realloc copies old memory contents, so I am not certain this would
     * work with a proper object that actaully does something in the 
     * constructor
     */
    path = new (raw_buffer) GPATH2;

    /* now we can write more elements of array */
    path->Count = 5;
    path->Nodes[4].x = rand();
    path->Nodes[4].y = rand();

    /* Because this is allocated with malloc/realloc, free it with free
     * rather than delete.
     * If 'path' was a proper object rather than a struct, you should
     * call the destructor manually first.
     */
    free(raw_buffer);

    return 0;
}

Granted, it's not idiomatic C++ as others have observed, but if the struct is part of legacy code it might be the most straightforward option.

Correctness of the above sample program has only been checked with valgrind using dummy definitions of the structs, your mileage may vary.

Sign up to request clarification or add additional context in comments.

2 Comments

According to my revised question: I think this is what I was looking for. I'll give it a try and see if it works.
@saunderl Hm, I'll see if I can update the answer to better answer the updated question then.
3

If it is fixed size write:

typedef struct 
{
char    SmType;
char    SRes;
float   SParm;
float   EParm;
WORD    Count;
char    Flags;
char    unused;
GPOINT2 Nodes[4];
} GPATH2;

if not fixed then change declaration to

GPOINT2* Nodes;

after creation or in constructor do

Nodes = new GPOINT2[size];

if you want to resize it you should use vector<GPOINT2>, because you can't resize array, only create new one. If you decide to do it, don't forget to delete previous one.

also typedef is not needed in c++, you can write

struct GPATH2
{
char    SmType;
char    SRes;
float   SParm;
float   EParm;
WORD    Count;
char    Flags;
char    unused;
GPOINT2 Nodes[4];
};

7 Comments

Your "not fixed" discussion would only be correct if the last member of the array was "GPOINT2 *Nodes"; "GPOINT2 Nodes[]" is not equivalent in this context.
Nodes = new GPOINT2[size]; (but at that point, you might as well use a vector)
@Zack @Roger Pate You are not correct. Having pointer instead of array (usually of 1 element) is not the same. You can not do what you can if there is GPOINT2 Nodes[1]. And you can not use vector instead of that.
@VJo i didn't understand anything
@Andrey google for "c stuct hack" and you will get million hits. The explanation is long and complex.
|
3

This appears to be a C99 idiom known as the "struct hack". You cannot (in standard C99; some compilers have an extension that allows it) declare a variable with this type, but you can declare pointers to it. You have to allocate objects of this type with malloc, providing extra space for the appropriate number of array elements. If nothing holds a pointer to an array element, you can resize the array with realloc.

Code that needs to be backward compatible with C89 needs to use

GPOINT2 Nodes[1];

as the last member, and take note of this when allocating.

This is very much not idiomatic C++ -- note for instance that you would have to jump through several extra hoops to make new and delete usable -- although I have seen it done. Idiomatic C++ would use vector<GPOINT2> as the last member of the struct.

12 Comments

+1, This is the most correct answer, but using vector is most likely not a solution if he needs a POD. Anyway, "struct hack" is just a stupid hack that should be avoided.
I have removed my answer where I claim such, because Clang and GCC say it's a GNU extension. I tried to find evidence in C99's spec to find where it forbids it, but I haven't found it.
@litb Corrected again, thanks. @VJo If you need the type to be POD, I don't think there's a better alternative.
@Johannes: Flexible array members are explicitly allowed by the C99 standard. Check out 6.7.2.1 paragraph 17.
That discussion's specifically about initializers for flexible array members. I don't have C99 on this computer or I would look for myself...
|
1

Arrays of unknown size are not valid as C++ data members. They are valid in C99, and your compiler may be mixing C99 support with C++.

What you can do in C++ is 1) give it a size, 2) use a vector or another container, or 3) ditch both automatic (local variable) and normal dynamic storage in order to control allocation explicitly. The third is particularly cumbersome in C++, especially with non-POD, but possible; example:

struct A {
  int const size;
  char data[1];

  ~A() {
    // if data was of non-POD type, we'd destruct data[1] to data[size-1] here
  }

  static auto_ptr<A> create(int size) {
    // because new is used, auto_ptr's use of delete is fine
    // consider another smart pointer type that allows specifying a deleter
    A *p = ::operator new(sizeof(A) + (size - 1) * sizeof(char));
    try {  // not necessary in our case, but is if A's ctor can throw
      new(p) A(size);
    }
    catch (...) {
      ::operator delete(p);
      throw;
    }
    return auto_ptr<A>(p);
  }

private:
  A(int size) : size (size) {
    // if data was of non-POD type, we'd construct here, being very careful
    // of exception safety
  }

  A(A const &other);             // be careful if you define these,
  A& operator=(A const &other);  // but it likely makes sense to forbid them

  void* operator new(size_t size);    // doesn't prevent all erroneous uses,
  void* operator new[](size_t size);  // but this is a start
};

Note you cannot trust sizeof(A) any where else in the code, and using an array of size 1 guarantees alignment (matters when the type isn't char).

Comments

1

This type of structure is not trivially useable on the stack, you'll have to malloc it. the significant thing to know is that sizeof(GPATH2) doesn't include the trailing array. so to create one, you'd do something like this:

GPATH2 *somePath;
size_t numPoints;

numPoints = 4;
somePath = malloc(sizeof(GPATH2) + numPoints*sizeof(GPOINT2));

I'm guessing GPATH2.Count is the number of elements in the Nodes array, so if it's up to you to initialize that, be sure and set somePath->Count = numPoints; at some point. If I'm mistaken, and the convention used is to null terminate the array, then you would do things just a little different:

somePath = malloc(sizeof(GPATH2) + (numPoints+1)*sizeof(GPOINT2));
somePath->Nodes[numPoints] = Some_Sentinel_Value;

make darn sure you know which convention the library uses.

As other folks have mentioned, realloc() can be used to resize the struct, but it will invalidate old pointers to the struct, so make sure you aren't keeping extra copies of it (like passing it to the library).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.