我正在学习VLA
,并编写了以下示例:
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;
}
所以我用
进行了测试char ca[3] = {'a', 'b', 'c'};
struct array_t *array_ptr = create(5, ca);
并且编译正常(不幸的是)。我发现6.7.6.2(p5)
:
如果大小是非整数常量的表达式 表达式:如果它出现在函数原型作用域的声明中, 它被视为由*代替;否则,每次 计算得出的值应大于零。
因此,显然n
不是常数表达式,const char data[n]
被简单地视为const char*
,这不是我想要的。
那么,如果这样的数组声明没有提供任何类型安全性,那么有什么理由吗?也许我们可以编写一些宏函数来执行以下操作:
#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
答案 0 :(得分:4)
首先,为具有灵活数组成员的结构分配空间的函数应如下所示:
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;
}
那么,如果这样的数组声明没有提供任何类型安全性,那么有什么理由吗?
从理论上讲,好的编译器可以省略警告,尽管我认为没有这样做。静态分析仪会发出警告。
但是,主要原因是自记录代码。您在size变量和array变量之间建立了紧密的耦合。
也许我们可以编写一些宏函数
当然,使用标准ISO C,我们可以编写包装宏以提高类型安全性并利用VLA表示法。像这样:
#define create_array_t(n, array) \
_Generic(&array, \
char(*)[n]: create, \
const char(*)[n]: create) (n, array)
这里的技巧是通过使用&来避开数组衰减,以获取数组指针。然后在使用传递的参数调用create
之前,比较数组类型是否与该指针匹配。
完整示例:
#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;
}
通过将array_t
设置为不透明类型,将struct实现隐藏在.c文件中,并获得带有私有封装的面向对象的ADT,可以进一步改善此效果。
答案 1 :(得分:0)
memcpy(&(array -> length), &n, sizeof(n)); memcpy(&(array -> data), data, n);
您在这里滥用了与编译器的合同。您保证不会更改任何结构成员,但会尝试找到解决方法。 这是非常糟糕的做法
因此,如果要在运行时分配或复制值,则不得声明为const
。否则,你做得最糟。您声明了const-只能在初始化期间分配,但是将其用作非const
对象。您破坏了正确的“常数”的逻辑。
如果要为此类结构动态分配内存,请不要使成员成为常量。
稍后,您可以在声明将使用该对象的其他函数时使指针指向const结构
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()
{
}