c-函数原型范围中的可变长度数组类型



我正在学习VLAs,并编写了以下示例:

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

首先,为具有灵活数组成员的结构体分配空间的函数应该是这样的:

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;
}

那么,如果这样的数组声明没有提供任何类型安全性,那么有什么原因吗?

好的编译器理论上可以省略警告,尽管我认为没有这样的编译器。静态分析仪将发出警告。

然而,主要原因是自文档化的代码。您可以在大小变量和数组变量之间创建紧密耦合。

也许我们可以写一些宏函数

当然,使用标准的ISOC,我们可以编写一个包装宏来提高类型安全性,并利用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成为不透明类型、将结构实现隐藏在.c文件中并获得具有私有封装的面向对象ADT来进一步改进。

memcpy(&(array -> length), &n, sizeof(n));
memcpy(&(array -> data), data, n);

您在这里滥用了与编译器的约定。你承诺不更改任何结构成员,但你试图找到解决方法。这是一种非常糟糕的做法

因此,如果要在运行时分配或复制值,则不能声明为const。否则你会做得最糟糕。您声明了一些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()
{
}

最新更新