【问题标题】:Variable length array types in function prototype scope函数原型范围内的可变长度数组类型
【发布时间】:2019-01-17 11:21:43
【问题描述】:

我正在了解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

【问题讨论】:

  • 只是为了好奇,您是指可变长度数组还是灵活数组成员
  • @StephanLechner 抱歉,措辞不好。肯定是灵活的数组成员。
  • 注意:说create(5, ca);,其中cachar [3],你不是违约吗?
  • 旁注,您不需要data_offset 的(容易出错的)业务。 sizeof(array_t) 给出了正确的结果,FAM 和所有的对齐。
  • ... 您仍然需要为 FAM 分配。 sizeof 替换 data_offset

标签: c arrays


【解决方案1】:

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

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

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

理论上,好的编译器可以忽略警告,但我认为没有任何警告。静态分析器会发出警告。

但是,主要原因是自记录代码。您在大小变量和数组变量之间创建了紧密耦合。

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

当然,使用标准 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 设为不透明类型、将结构实现隐藏在 .c 文件中并获得具有私有封装的面向对象的 ADT 来进一步改进。

【讨论】:

  • 看到_Generic 的有用用例非常有趣。但正如我在标准The type name in a generic association shall specify a complete object type other than a variably modified type 中看到的那样。我尝试了以下char data[5]; const size_t n = 5; create_array_t(n, data);,但它无法编译。如果这种使用通用选择的 VM 类型的问题是 UB,我看到的错误消息是 GCC 的实现细节?
  • @SomeName 哎呀,我不知道!好吧,这意味着上面的代码可以工作,但不是 VLA。使用 VLA 不是 UB,而是更糟糕的是,违反了约束。我发誓,委员会必须积极努力使所有新的语言功能尽可能无用。
【解决方案2】:
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()
{
}

【讨论】:

  • 问题询问函数参数,变长数组 这个答案没有回答这个问题。它解决的问题可以在评论中提出,但它没有回答问题。
  • @EricPostpischil 我不同意。
  • 这个答案在哪里说明了参数?该问题在哪里询问有关结构的任何内容?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-05
  • 2019-03-14
  • 2014-05-04
  • 2021-07-17
  • 2015-09-15
  • 2011-12-08
  • 1970-01-01
相关资源
最近更新 更多