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

时间:2019-01-17 11:21:43

标签: c arrays

我正在学习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

2 个答案:

答案 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()
{
}