0

I'm learning about VLAs and wrote the following example:

struct array_t{
    const size_t length;
    const char data[];
};

struct array_t *create(const size_t n, const char data[n]){
    const size_t data_offset = offsetof(struct array_t, data);
    struct array_t *array = malloc(data_offset + n * sizeof(char));
    memcpy(&(array -> length), &n, sizeof(n));
    memcpy(&(array -> data), data, n);
    return array;
}

So I tested it with

char ca[3] = {'a', 'b', 'c'};
struct array_t *array_ptr = create(5, ca);

and it compiles fine (unfortunately). As I figured out 6.7.6.2(p5):

If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by *; otherwise, each time it is evaluated it shall have a value greater than zero.

So obviously n is not a constant expression and const char data[n] is simply treated as const char* which is not what I wanted.

So is there any reason of such arrays declarations if they don't give any type safety? Maybe we can write some macro function that will do the following:

#define create_array_t //...

const char a1[5];
const char a2[10];
const char *a_ptr;

create_array_t(5, a1); //fine
create_array_t(5, a2); //error
create_array_t(5, a_ptr); //error
7
  • 3
    Just for curiosity, do you mean variable length arrays or flexible array members? Commented Jan 17, 2019 at 11:24
  • @StephanLechner Sorry, bad wording. Flexible array member for sure. Commented Jan 17, 2019 at 11:24
  • Note: by saying create(5, ca);, where ca is of char [3], aren't you breaking the contract? Commented Jan 17, 2019 at 11:30
  • Side note, you don't need the (error prone) business with data_offset. sizeof(array_t) gives the correct result, alignment for the FAM and all. Commented Jan 17, 2019 at 11:34
  • 1
    ... You still need to allocate for the FAM. The sizeof replaces data_offset Commented Jan 17, 2019 at 11:38

2 Answers 2

4

First of all, the function allocating room for a struct with a flexible array member should be like this:

array_t* create (const size_t n, const char data[n])
{
  array_t* array = malloc( sizeof(array_t) + sizeof(char[n]) );
  array->length = n;
  memcpy(array->data, data, n);
  return array;
}

So is there any reason of such arrays declarations if they don't give any type safety?

Good compilers can theoretically omit warnings, though I don't think there are any that does. Static analysers will warn.

However, the main reason is self-documenting code. You create a tight coupling between the size variable and the array variable.

Maybe we can write some macro function

Sure, with standard ISO C we can write a wrapper macro to increase type safety and take advantage of the VLA notation. Something like this:

#define create_array_t(n, array)      \
  _Generic(&array,                    \
           char(*)[n]:       create,  \
           const char(*)[n]: create) (n, array)

The trick here is to dodge the array decay by using &, to take an array pointer. Then compare if the array type matches that pointer, before calling create with the parameters passed.

Full example:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>

typedef struct 
{
  size_t length;
  char data[];
} array_t;

array_t* create (const size_t n, const char data[n])
{
  array_t* array = malloc(sizeof(array_t) + sizeof(char[n]));
  array->length = n;
  memcpy(array->data, data, n);
  return array;
}

#define create_array_t(n, array)      \
  _Generic(&array,                    \
           char(*)[n]:       create,  \
           const char(*)[n]: create) (n, array)

int main (void)
{
  const char a1[5];
  const char a2[10];
  const char *a_ptr;

  (void) create_array_t(5, a1);    // fine
//(void) create_array_t(5, a2);    // error _Generic selector of type 'const char(*)[10]' is not compatible
//(void) create_array_t(5, a_ptr); // error _Generic selector of type 'const char**' is not compatible

  return 0;
}

This can be further improved by making array_t an opaque type, hiding the struct implementation inside a .c file and get an object-oriented ADT with private encapsulation.

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

2 Comments

Very interesting to see useful use-case of _Generic. But as I read in the Standard The type name in a generic association shall specify a complete object type other than a variably modified type. I tried the following char data[5]; const size_t n = 5; create_array_t(n, data); and it does not compile. The question if such usage of VM types with Generic Selection is UB and the error message I see is the implementation details of GCC?
@SomeName Yikes, I didn't know that! Well, that means that the above code will work, but not a VLA. Using VLA isn't UB but worse, a constraint violation. I swear, the committee must actively be striving to make all new language features as useless as possible.
0
memcpy(&(array -> length), &n, sizeof(n));
memcpy(&(array -> data), data, n);

You abuse here the contract with the compiler. You promise to do not change any of struct members but you try to find the workaround for it. It is an extremely bad practice

So if you want to assign or copy values runtime you must not declare it const. Otherwise you do the worst. You declare something const - which can be assigned only during the initialisation, but you use it as a not const object. You break the logic of correct "constantness."

If you want to dynamically allocate memory for such a structs do not make members const.

You can later make the pointer to the const struct when declaring another functions which will use that object

typedef struct{
    size_t length;
    char data[];
}array_t;

array_t *create(const size_t n, const char data[n])
{
    array_t *array = malloc(sizeof(array_t) + n);
    array -> length = n;
    memcpy(array -> data, data, n);
    return array;
}

void do_something(const array_t *prt)
{
    ....
}

int main()
{
}

3 Comments

The question asks about the function parameter, a variable length array This answer does not answer that question. The issue it addresses could have been raised in a comment, but it does not answer the question.
@EricPostpischil I do not agree.
Where does this answer say anything about the parameter? Where does the question ask anything about the structure?

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.