Zero-size array as struct member

T

thomas

Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
---------

Once I declared a struct like this to store server list info.
It's supposed to be used like this.

----------
SvrList* pList = (SvrList*)malloc(sizeof(
SvrList) + svrNum*sizeof(GameSvr));
pList->uNum, pList->svr[0], pList->svr[1].... blabla..
---------

The vs2005 compiler gives me the "nonstandard extension used : zero-
sized array in struct/union" warning though.

I may keep my eye closed to the warning since everything looks fine.
But I don't know whether anything bad may happen to me some day due to
running environment changes(porting to different platforms, or any
other conditions).

One work-around way is to declare a one-sized array as struct member,
but I didn't see any substantial changes except no warning in this
case.

Any body give some suggestions?
 
J

Julián Rodríguez Bajo

El 18/08/2010 13:30, thomas escribió:
Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
---------

Once I declared a struct like this to store server list info.
It's supposed to be used like this.

----------
SvrList* pList = (SvrList*)malloc(sizeof(
SvrList) + svrNum*sizeof(GameSvr));
pList->uNum, pList->svr[0], pList->svr[1].... blabla..
---------

The vs2005 compiler gives me the "nonstandard extension used : zero-
sized array in struct/union" warning though.

I may keep my eye closed to the warning since everything looks fine.
But I don't know whether anything bad may happen to me some day due to
running environment changes(porting to different platforms, or any
other conditions).

One work-around way is to declare a one-sized array as struct member,
but I didn't see any substantial changes except no warning in this
case.

Any body give some suggestions?

You can change to

struct SvrList{
unsigned int uNum;
GameSvr *svr; //line A
};

and still use array indexes.
 
T

thomas

El 18/08/2010 13:30, thomas escribió:


Hi, I need your help.
----------
struct SvrList{
     unsigned int uNum;
     GameSvr  svr[0];            //line A
};
---------
Once I declared a struct like this to store server list info.
It's supposed to be used like this.
----------
SvrList* pList = (SvrList*)malloc(sizeof(
SvrList) + svrNum*sizeof(GameSvr));
pList->uNum, pList->svr[0], pList->svr[1].... blabla..
---------
The vs2005 compiler gives me the "nonstandard extension used : zero-
sized array in struct/union" warning though.
I may keep my eye closed to the warning since everything looks fine.
But I don't know whether anything bad may happen to me some day due to
running environment changes(porting to different platforms, or any
other conditions).
One work-around way is to declare a one-sized array as struct member,
but I didn't see any substantial changes except no warning in this
case.
Any body give some suggestions?

You can change to

struct SvrList{
      unsigned int uNum;
      GameSvr  *svr;            //line A

};

and still use array indexes.

Thanks, that works.
But I will call "new" two times: one to allocate memory for struct
SvrList, one for "*svr".

I really want to know whether there's any side effect if I use zero-
sized member.
 
V

Vladimir Jovic

thomas said:
Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};

I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and that is UB.
How come your program is not crashing, or at least going crazy? Maybe
you are just unlucky to have a bug hidden.
 
J

Jeff Flinn

Vladimir said:
thomas said:
Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};

I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and that is UB.
How come your program is not crashing, or at least going crazy? Maybe
you are just unlucky to have a bug hidden.

It's an old C programmers hack. I've come across this idiom in lot's old
C code, particularly driver and os code. Microsoft Win32 is rife with it.

Jeff
 
J

Juha Nieminen

Vladimir Jovic said:
thomas said:
Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};

I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and that is UB.
How come your program is not crashing, or at least going crazy?

pList->svr[0] is accessing memory allocated by the malloc() call,
hence it can't crash (well, at least if 'GameSvr' is a POD type).
 
J

Juha Nieminen

Fred Zwarts said:
You can use std::vector. A vector can have 0 or more elements.
It also removes the need for malloc and free.

And also makes the struct something like 10 times slower to allocate
and deallocate, as well as consuming more memory.
 
V

Vladimir Jovic

Juha said:
Vladimir Jovic said:
thomas said:
Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and that is UB.
How come your program is not crashing, or at least going crazy?

pList->svr[0] is accessing memory allocated by the malloc() call,
hence it can't crash (well, at least if 'GameSvr' is a POD type).

If the array's size is zero, how can you access even one element?
The example similar to the original example, except this one compiles :

#include <iostream>
using namespace std;
struct A
{
int a;
int b[0];
};
int main()
{
A *p = new A;

p->a = 5;
p->b[0] = 6;
p->b[1] = 7;
p->b[2] = 8;

std::cout << "p->a="<<p->a << std::endl
<< "p->b[0]="<<p->b[0] << std::endl
<< "p->b[1]="<<p->b[1] << std::endl
<< "p->b[2]="<<p->b[2] << std::endl;

delete( p );
}



And the output :

../a.out
p->a=5

p->b[0]=6

p->b[1]=7

p->b[2]=8
*** glibc detected *** ./a.out: free(): invalid next size (fast):
0x087b8008 ***
======= Backtrace: =========
/lib/libc.so.6[0x260874]
/lib/libc.so.6(cfree+0x96)[0x2628d6]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x1ba461]
../a.out(__gxx_personality_v0+0x24a)[0x8048886]
/lib/libc.so.6(__libc_start_main+0xe6)[0x2095d6]
../a.out(__gxx_personality_v0+0x35)[0x8048671]
======= Memory map: ========
00101000-001e8000 r-xp 00000000 fd:07 442834
/usr/lib/libstdc++.so.6.0.10
001e8000-001ec000 r--p 000e6000 fd:07 442834
/usr/lib/libstdc++.so.6.0.10
001ec000-001ee000 rw-p 000ea000 fd:07 442834
/usr/lib/libstdc++.so.6.0.10
001ee000-001f3000 rw-p 001ee000 00:00 0
001f3000-00356000 r-xp 00000000 fd:00 58475 /lib/libc-2.8.so
00356000-00358000 r--p 00163000 fd:00 58475 /lib/libc-2.8.so
00358000-00359000 rw-p 00165000 fd:00 58475 /lib/libc-2.8.so
00359000-0035c000 rw-p 00359000 00:00 0
00821000-0083d000 r-xp 00000000 fd:00 58474 /lib/ld-2.8.so
0083d000-0083e000 r--p 0001c000 fd:00 58474 /lib/ld-2.8.so
0083e000-0083f000 rw-p 0001d000 fd:00 58474 /lib/ld-2.8.so
00848000-00849000 r-xp 00848000 00:00 0 [vdso]
009ac000-009d3000 r-xp 00000000 fd:00 58481 /lib/libm-2.8.so
009d3000-009d4000 r--p 00026000 fd:00 58481 /lib/libm-2.8.so
009d4000-009d5000 rw-p 00027000 fd:00 58481 /lib/libm-2.8.so
00db3000-00dc0000 r-xp 00000000 fd:00 58490
/lib/libgcc_s-4.3.0-20080428.so.1
00dc0000-00dc1000 rw-p 0000c000 fd:00 58490
/lib/libgcc_s-4.3.0-20080428.so.1
08048000-08049000 r-xp 00000000 fd:09 4555963
/sandbox/vladimir/data_create/a.out
08049000-0804a000 rw-p 00000000 fd:09 4555963
/sandbox/vladimir/data_create/a.out
087b8000-087d9000 rw-p 087b8000 00:00 0 [heap]
b7f00000-b7f21000 rw-p b7f00000 00:00 0
b7f21000-b8000000 ---p b7f21000 00:00 0
b80a5000-b80a8000 rw-p b80a5000 00:00 0
b80c7000-b80c8000 rw-p b80c7000 00:00 0
bfe96000-bfeab000 rw-p bffeb000 00:00 0 [stack]
Aborted
 
B

Bo Persson

Juha said:
And also makes the struct something like 10 times slower to
allocate and deallocate, as well as consuming more memory.

Yes, but you get rid of the SvrList (which keeps size and buffer) if
you let the std::vector do the job. You don't have to dynamically
allocate the vector itself.


Bo Persson
 
J

Joe Greer

If the array's size is zero, how can you access even one element?

What's your point? Why would you do that? If b were a vector, you would
get similar behavior. That is, it is just as undefined if you don't
bother to check the size of the vector before assigning data. This is a
idiom used by device drivers to return information requiring a minimum
number of allocations and a minimum number of frees. It is always used
with malloc and the size of the array is always embedded in the struct
somewhere so that you don't go out of bounds. So, to fix up your code
below.
The example similar to the original example, except this one compiles :

#include <iostream>
using namespace std;
struct A
{
int a; int b_len;
int b[0];
};

A * AFactory(int sz)
{
A * p = (A *)malloc(sizeof(A) + sz * sizeof(b[0]));
p->b_len = sz;
return p
}
int main()
{
A *p = new A;
A *p = AFactory(0);
p->a = 5;

// We know we didn't allocate space so we wouldn't bother with
this, but...
if (p->b_len >= 3)
{
p->b[0] = 6;
p->b[1] = 7;
p->b[2] = 8; }

std::cout << "p->a="<<p->a << std::endl;
free(p); // you could also have function for this, but
deallocation doesn't require additional info.

I would never advocate the above as a C++ application pattern, but if you
are messing in drivers and kernel, then every clock cycle counts and
multiple heap hits can kill performance.

This is one of the places I would really like to see some sort of VLA
available (at least if it allocated memory in this fashion). The nice
thing is that it is one heap allocation and free. Rolling your own as
above is a bit of a pain. Fortunately, I haven't had to do stuff like
that in years.

joe
 
A

Andrey Tarasevich

thomas said:
Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
---------

Once I declared a struct like this to store server list info.
It's supposed to be used like this.

----------
SvrList* pList = (SvrList*)malloc(sizeof(
SvrList) + svrNum*sizeof(GameSvr));
pList->uNum, pList->svr[0], pList->svr[1].... blabla..
---------

The vs2005 compiler gives me the "nonstandard extension used : zero-
sized array in struct/union" warning though.
...
One work-around way is to declare a one-sized array as struct member,
but I didn't see any substantial changes except no warning in this
case.

Well, declaring a 1-sized array is the way it is normally done. The
memory allocation is supposed to be done as follows

SvrList* pList = (SvrList*) malloc(offsetof(SvrList, svr) +
svrNum * sizeof(GameSvr));

As you can notice, you can declare the array with any non-zero size in
this case, since the above total size calculation approach is not
affected by the declared array size.
 
V

Vladimir Jovic

Joe said:
What's your point? Why would you do that? If b were a vector, you would
get similar behavior. That is, it is just as undefined if you don't
bother to check the size of the vector before assigning data. This is a

Will next assertion pass or fail :
int a[0];
assert( sizeof( a ) == 0 );
?
What bothers me is how can you use an array of size zero.
idiom used by device drivers to return information requiring a minimum
number of allocations and a minimum number of frees. It is always used
with malloc and the size of the array is always embedded in the struct
somewhere so that you don't go out of bounds. So, to fix up your code
below.
The example similar to the original example, except this one compiles :

#include <iostream>
using namespace std;
struct A
{
int a; int b_len;
int b[0];
};

A * AFactory(int sz)
{
A * p = (A *)malloc(sizeof(A) + sz * sizeof(b[0]));
p->b_len = sz;
return p
}

This looks like a hack. Why declare an array of size 0? Why not use a
pointer (like someone suggested)? Is it another (now obsolete and
depreciated) way of declaring pointers?
 
J

James Kanze

Vladimir said:
thomas said:
Hi, I need your help.
----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and
that is UB. How come your program is not crashing, or at
least going crazy? Maybe you are just unlucky to have a bug
hidden.
It's an old C programmers hack. I've come across this idiom in
lot's old C code, particularly driver and os code. Microsoft
Win32 is rife with it.

Except that it's not legal C, either. The C language was
carefully specified so that a bounds checking implementation
would be legal, and the above will fail in such cases.

In C++, of course, the declaration he's looking for is
std::vector<SvrList>, without any manual new.
 
B

Bart van Ingen Schenau

Joe said:
@news.albasani.net:
What's your point?  Why would you do that?  If b were a vector, you would
get similar behavior.  That is, it is just as undefined if you don't
bother to check the size of the vector before assigning data. This is a

Will next assertion pass or fail :
        int a[0];
        assert( sizeof( a ) == 0 );
?

That snippet should not compile, because it is not allowed to define
objects with size 0.
What bothers me is how can you use an array of size zero.

You can't, except as a non-standard predecessor of the 'flexible array
member' of C99.
Some compilers will allow a zero-sized array as the last member of a
structure as a way to let the structure contain an array of
unspecified size. It is the full responsibility of the programmer to
ensure enough space gets allocated or the number of array members that
is actually needed.

A * AFactory(int sz)
{
   A * p = (A *)malloc(sizeof(A) + sz * sizeof(b[0]));
   p->b_len = sz;
   return p
}

This looks like a hack. Why declare an array of size 0? Why not use a
pointer (like someone suggested)? Is it another (now obsolete and
depreciated) way of declaring pointers?

It is a hack, and it is commonly known as the 'struct hack'.
The reasons for not using a pointer can be very diverse.
In the (C-)code that I am currently working with, this hack is used in
the definition of the messages that are passed between different parts
of the system.
As the system consists of multiple (embedded) processors, it is a
requirement that a message is always fully contained within a single
contiguous buffer, so that it can be easily transported to a different
processor. This also rules out the use of internal pointers within the
buffer.

Bart v Ingen Schenau
 
J

James Kanze

Vladimir Jovic said:
thomas said:
Hi, I need your help.
----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and
that is UB. How come your program is not crashing, or at
least going crazy?
pList->svr[0] is accessing memory allocated by the malloc()
call, hence it can't crash (well, at least if 'GameSvr' is
a POD type).

pList->svr[0] is accessing beyond the end of the array, hence,
the code is undefined behavior, both in C and in C++. The
C language has been carefully specified to allow bounds checking
implementations---there has been (and maybe still is) one.

In C99, flexible array members were introduced to support this
idiom. In C++, std::vector does the job a lot cleaner.
 
J

Jeff Flinn

James said:
Vladimir said:
thomas wrote:
Hi, I need your help.
----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
};
---------
Once I declared a struct like this to store server list info.
It's supposed to be used like this.
----------
SvrList* pList = (SvrList*)malloc(sizeof(
SvrList) + svrNum*sizeof(GameSvr));
pList->uNum, pList->svr[0], pList->svr[1].... blabla..
I wouldn't call this fine. Even
pList->svr[0]
is accessing the element that is out of array's bounds, and
that is UB. How come your program is not crashing, or at
least going crazy? Maybe you are just unlucky to have a bug
hidden.
It's an old C programmers hack. I've come across this idiom in
lot's old C code, particularly driver and os code. Microsoft
Win32 is rife with it.

Except that it's not legal C, either. The C language was
carefully specified so that a bounds checking implementation
would be legal, and the above will fail in such cases.

Hence the term 'hack'. ;-)
In C++, of course, the declaration he's looking for is
std::vector<SvrList>, without any manual new.

Exactly, unless the op is dealing with some of that legacy Hacked code.

Jeff
 
G

Goran Pusic

Hi, I need your help.

----------
struct SvrList{
    unsigned int uNum;
    GameSvr  svr[0];            //line A};

---------

Once I declared a struct like this to store server list info.
It's supposed to be used like this.

----------
SvrList* pList = (SvrList*)malloc(sizeof(
SvrList) + svrNum*sizeof(GameSvr));
pList->uNum, pList->svr[0], pList->svr[1].... blabla..
---------

The vs2005 compiler gives me the "nonstandard extension used : zero-
sized array in struct/union" warning though.

I may keep my eye closed to the warning since everything looks fine.
But I don't know whether anything bad may happen to me some day due to
running environment changes(porting to different platforms, or any
other conditions).

One work-around way is to declare a one-sized array as struct member,
but I didn't see any substantial changes except no warning in this
case.

Any body give some suggestions?

If you ever plan to change actual number of elements in your object,
just drop this crap and use a vector. If not, ponder this (I am
presuming that your uNum is number of elements you have there):

template<typename T>
// hbvla: Heap Based Variable Length Array
class hbvla : public boost::noncopyable // Can't assign, nor copy-
construct.
{
public:
void operator delete(void* p)
{
delete reinterpret_cast<char*>(p);
}
void operator delete(void* p, size_t /*cElements*/)
{ // Compiler calls this in case of exception in ctor
// that gets called through overloaded operator new (see ^^^).
// (note matched size_t cElements argument in two cases)
delete reinterpret_cast<char*>(p);
}

static hbvla* create(size_t cElements) { return new (cElements)
hbvla(cElements); }

~hbvla()
{
destroy_elements();
}

// Trivial helper methods.
size_t size() const { return _size; }
T& operator[](size_t i) { assert(i < size()); return _elements; }
const T& operator[](size_t i) const { assert(i < size()); return
_elements; }
T* begin() { return _elements; }
const T* begin() const { return _elements; }
T* end() { return _elements+size(); }
const T* end() const { return _elements+size(); }

private:
size_t _size;
T _elements[1]; // Standard does not allow 0.
// 1 is OK, but I could have put anything due to particular design.

// ^^^
void* operator new(size_t objectSize, size_t cElements)
{ // Only this operator new shall be used.

// Handle overflow.
const size_t MaxElements = (SIZE_MAX-objectSize)/cElements;
if (cElements > MaxElements)
throw std::bad_alloc();

return new char[objectSize + cElements*sizeof(T)];
}

/*
Using "standard" operators new with hbvla is does not work, so hide
them
(and don't implement them).

The [] form is not really needed in this case because default ctor
is hidden,
but I am leaving it here for informative purposes.

operator delete[] does not work either.
*/
void* operator new[](size_t objectSize);
void* operator new(size_t objectSize);
void operator delete[](void*);

typedef hbvla<T> this_type; // helper typedef.

// ctor: we must empty-construct our _elements from 1 to
(cElements-1).
// Empty ctor is dangerous. But since we have a ctor, we don't need
to write and hide anything.
hbvla(size_t cElements) : _size(0)
{
if (!cElements)
return; // Strange but possible.

// Construct elements from 1 to (cElements-1).
// Use ScopeGuard to destruct partially-constructed array of
elements.
_size = 1;
ScopeGuard elementsGuard = MakeObjGuard(*this,
&this_type::destroy_elements);
while (_size<cElements)
{
T* p = &_elements[_size];
new (p) T;
_size++;
}
elementsGuard.Dismiss();
}

void destroy_elements()
{ // destroy elements that we create "by hand"
// (C++ runtime destroys _element[0]).
std::for_each(begin()+1, end(), &this_type::destroy);
}

static void destroy(const T& element)
{
element.~T();
}
};

Goran.
 
J

Jorgen Grahn

Hi, I need your help.

----------
struct SvrList{
unsigned int uNum;
GameSvr svr[0]; //line A
}; ....

The vs2005 compiler gives me the "nonstandard extension used : zero-
sized array in struct/union" warning though.

I may keep my eye closed to the warning since everything looks fine.
But I don't know whether anything bad may happen to me some day due to
running environment changes(porting to different platforms, or any
other conditions).

One work-around way is to declare a one-sized array as struct member,
but I didn't see any substantial changes except no warning in this
case.

That's a substantial change, IMHO. Take gcc for example. There you
have (last time I checked) no way to disable that warning without
disabling C++98 conformance. That can be *really* annoying, when you
have seen the warning daily for a year or two ...

/Jorgen
 
J

Joe Greer

This looks like a hack. Why declare an array of size 0? Why not use a
pointer (like someone suggested)? Is it another (now obsolete and
depreciated) way of declaring pointers?

Indeed, it is a hack, but a useful one. Usually the array is declared of
size 1 and not 0, but that doesn't make it any prettier. You can't use a
pointer because that would require 2 allocations and doesn't pass through
the network near as well. This is a mechanism for creating a struct with
some dynamically sized data using only 1 buffer. Memory allocation is not
in anyway cheap (not only to you have the usual overhead, but there is also
a mutex in there somewhere and possibly a kernel transistion) and in some
cases, one needs to have only one. This sort of thing can be the
difference between passing your data in hours instead of days for some
kinds of applications. You certainly would only use this technique if
profiling indicated that it was required, but it is useful to understand
it.

joe
 
J

Joe Greer

In C99, flexible array members were introduced to support this
idiom. In C++, std::vector does the job a lot cleaner.

Not really. This hack is used to reduce memory allocations. Using a
vector doesn't really do that.

joe
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top