Типы массивов переменной длины в области действия прототипа функции - PullRequest
0 голосов
/ 17 января 2019

Я изучаю VLA s и написал следующий пример:

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 голосов
/ 17 января 2019

Прежде всего, функция, выделяющая место для структуры с гибким членом массива, должна выглядеть следующим образом:

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 непрозрачным типом, скрыв реализацию struct внутри файла .c и получив объектно-ориентированный ADT с закрытой инкапсуляцией.

0 голосов
/ 17 января 2019
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()
{
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...