Are flexible array members valid in C++?

C++Flexible Array-Member

C++ Problem Overview


In C99, you can declare a flexible array member of a struct as such:

struct blah
{
    int foo[];
};

However, when someone here at work tried to compile some code using clang in C++, that syntax did not work. (It had been working with MSVC.) We had to convert it to:

struct blah
{
    int foo[0];
};

Looking through the C++ standard, I found no reference to flexible member arrays at all; I always thought [0] was an invalid declaration, but apparently for a flexible member array it is valid. Are flexible member arrays actually valid in C++? If so, is the correct declaration [] or [0]?

C++ Solutions


Solution 1 - C++

C++ was first standardized in 1998, so it predates the addition of flexible array members to C (which was new in C99). There was a corrigendum to C++ in 2003, but that didn't add any relevant new features. The next revision of C++ (C++2b) is still under development, and it seems flexible array members still aren't added to it.

Solution 2 - C++

C++ doesn't support C99 flexible array members at the end of structures, either using an empty index notation or a 0 index notation (barring vendor-specific extensions):

struct blah
{
    int count;
    int foo[];  // not valid C++
};

struct blah
{
    int count;
    int foo[0]; // also not valid C++
};

As far as I know, C++0x will not add this, either.

However, if you size the array to 1 element:

struct blah
{
    int count;
    int foo[1];
};

the code will compile, and work quite well, but it is technically undefined behavior. You can allocate the appropriate memory with an expression that is unlikely to have off-by-one errors:

struct blah* p = (struct blah*) malloc( offsetof(struct blah, foo[desired_number_of_elements]);
if (p) {
    p->count = desired_number_of_elements;

    // initialize your p->foo[] array however appropriate - it has `count`
    // elements (indexable from 0 to count-1)
}

So it's portable between C90, C99 and C++ and works just as well as C99's flexible array members.

Raymond Chen did a nice writeup about this: Why do some structures end with an array of size 1?

Note: In Raymond Chen's article, there's a typo/bug in an example initializing the 'flexible' array. It should read:

for (DWORD Index = 0; Index < NumberOfGroups; Index++) { // note: used '<' , not '='
  TokenGroups->Groups[Index] = ...;
}

Solution 3 - C++

The second one will not contain elements but rather will point right after blah. So if you have a structure like this:

struct something
{
  int a, b;
  int c[0];
};

you can do things like this:

struct something *val = (struct something *)malloc(sizeof(struct something) + 5 * sizeof(int));
val->a = 1;
val->b = 2;
val->c[0] = 3;

In this case c will behave as an array with 5 ints but the data in the array will be after the something structure.

The product I'm working on uses this as a sized string:

struct String
{
  unsigned int allocated;
  unsigned int size;
  char data[0];
};

Because of the supported architectures this will consume 8 bytes plus allocated.

Of course all this is C but g++ for example accepts it without a hitch.

Solution 4 - C++

If you can restrict your application to only require a few known sizes, then you can effectively achieve a flexible array with a template.

template <typename BASE, typename T, unsigned SZ>
struct Flex : public BASE {
    T flex_[SZ];
};

Solution 5 - C++

If you only want

struct blah { int foo[]; };

then you don't need the struct at all an you can simply deal with a malloc'ed/new'ed int array.

If you have some members at the beginning:

struct blah { char a,b; /*int foo[]; //not valid in C++*/ };

then in C++, I suppose you could replace foo with a foo member function:

struct blah { alignas(int) char a,b; 
	int *foo(void) { return reinterpret_cast<int*>(&this[1]); } };

Example use:

#include <stdlib.h>
struct blah { 
	alignas(int) char a,b; 
	int *foo(void) { return reinterpret_cast<int*>(&this[1]); }
};
int main()
{
	blah *b = (blah*)malloc(sizeof(blah)+10*sizeof(int));
	if(!b) return 1;
	b->foo()[1]=1;
}

Solution 6 - C++

A proposal is underway, and might make into some future C++ version. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1039r0.html for details (the proposal is fairly new, so it's subject to changes)

Solution 7 - C++

I faced the same problem to declare a flexible array member which can be used from C++ code. By looking through glibc headers I found that there are some usages of flexible array members, e.g. in struct inotify which is declared as follows (comments and some unrelated members omitted):

struct inotify_event
{
  //Some members
  char name __flexarr;
};

The __flexarr macro, in turn is defined as

/* Support for flexible arrays.
   Headers that should use flexible arrays only if they're "real"
   (e.g. only if they won't affect sizeof()) should test
   #if __glibc_c99_flexarr_available.  */
#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
# define __flexarr	[]
# define __glibc_c99_flexarr_available 1
#elif __GNUC_PREREQ (2,97)
/* GCC 2.97 supports C99 flexible array members as an extension,
   even when in C89 mode or compiling C++ (any version).  */
# define __flexarr	[]
# define __glibc_c99_flexarr_available 1
#elif defined __GNUC__
/* Pre-2.97 GCC did not support C99 flexible arrays but did have
   an equivalent extension with slightly different notation.  */
# define __flexarr	[0]
# define __glibc_c99_flexarr_available 1
#else
/* Some other non-C99 compiler.  Approximate with [1].  */
# define __flexarr	[1]
# define __glibc_c99_flexarr_available 0
#endif

I'm not familar with MSVC compiler, but probably you'd have to add one more conditional macro depending on MSVC version.

Solution 8 - C++

Flexible arrays are not part of the C++ standard yet. That is why int foo[] or int foo[0] may not compile. While there is a proposal being discussed, it has not been accepted to the newest revision of C++ (C++2b) yet.

However, almost all modern compiler do support it via compiler extensions.

The catch is that if you use this extension with the highest warning level (-Wall --pedantic), it may result into a warning.

A workaround to this is to use an array with one element and do access out of bounds. While this solution is UB by the spec (dcl.array and expr.add), most of the compilers will produce valid code and even clang -fsanitize=undefined is happy with it:

#include <new>
#include <type_traits>

struct A {
    int a[1];
};

int main()
{
    using storage_type = std::aligned_storage_t<1024, alignof(A)>;
    static storage_type memory;
    
    A *ptr_a = new (&memory) A;

    ptr_a->a[2] = 42;
    
    return ptr_a->a[2];
}

demo


Having all that said, if you want your code to be standard compliant and do not depend on any compiler extension, you will have to avoid using this feature.

Solution 9 - C++

Flexible array members are not supported in standard C++, however the clang documentation says.

"In addition to the language extensions listed here, Clang aims to support a broad range of GCC extensions."

The gcc documentation for C++ says.

"The GNU compiler provides these extensions to the C++ language (and you can also use most of the C language extensions in your C++ programs)."

And the gcc documentation for C documents support for arrays of zero length.

https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

Solution 10 - C++

The better solution is to declare it as a pointer:

struct blah
{
    int* foo;
};

Or better yet, to declare it as a std::vector:

struct blah
{
    std::vector<int> foo;
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMSNView Question on Stackoverflow
Solution 1 - C++Martin v. LöwisView Answer on Stackoverflow
Solution 2 - C++Michael BurrView Answer on Stackoverflow
Solution 3 - C++terminusView Answer on Stackoverflow
Solution 4 - C++jxhView Answer on Stackoverflow
Solution 5 - C++PSkocikView Answer on Stackoverflow
Solution 6 - C++pqnetView Answer on Stackoverflow
Solution 7 - C++St.AntarioView Answer on Stackoverflow
Solution 8 - C++ivaigultView Answer on Stackoverflow
Solution 9 - C++plugwashView Answer on Stackoverflow
Solution 10 - C++Zac HowlandView Answer on Stackoverflow