Reputation: 9540
I'm learning about VLA
s 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
Upvotes: 0
Views: 160
Reputation: 214310
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.
Upvotes: 4
Reputation: 67749
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()
{
}
Upvotes: 0