Корректность констант для указателей массива?

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

void func (size_t n, int (*arr)[n]);
...

int array [3];
func(3, &array);

Это звучало так, что потенциально это может быть хорошей идеей, чтобы предотвратить все виды ошибок, связанных с типом и массив-вне границ. Но потом мне пришло в голову, что я не знаю, как применить к нему константную корректность.

Если я делаю void func (size_t n, const int (*arr)[n]), то он является const правильным. Но тогда я больше не могу передавать массив из-за несовместимых типов указателей. int (*)[3] против const int (*)[3]. Квалификатор относится к указанным данным, а не к самому указателю.

Явное приведение в вызывающем абоненте разрушит всю идею повышенной безопасности типов.

Как применить константную корректность к указателям массива, переданным в качестве параметров? Возможно ли это?


ИЗМЕНИТЬ

Как и информация, кто-то сказал, что идея передачи массивов указателем вроде этого, вероятно, происходит из MISRA С++: 2008 5-2-12. См. Например PRQA с высокой степенью честности.

Ответы

Ответ 1

Нет никакого способа сделать это, кроме броска. Это является существенным недостатком идеи передать массивы таким образом.

Вот аналогичный поток, где правила C сравниваются с правилами С++. Из этого сравнения можно сделать вывод, что правила C не так хорошо разработаны, потому что ваш вариант использования действителен, но C не допускает неявного преобразования. Другим примером является преобразование T ** в T const * const *; это безопасно, но C не разрешено.

Обратите внимание, что поскольку n не является постоянным выражением, то int n, int (*arr)[n] не имеет никакой безопасности добавленного типа по сравнению с int n, int *arr. Вы по-прежнему знаете длину (n), и по-прежнему неактуально поведение undefined для доступа к границам и поведение undefined для передачи массива, который фактически не является длиной n.

Этот метод имеет большее значение в случае передачи не-VLA-массивов, когда компилятор должен сообщить, передаете ли указатель массиву неправильной длины.

Ответ 2

Стандарт C говорит, что (раздел: §6.7.3/9):

Если спецификация типа массива включает в себя квалификаторы любого типа, тип элемента имеет соответствующую квалификацию, не тип массива. [...]

Следовательно, в случае const int (*arr)[n], const применяется к элементам массива вместо самого массива arr. arr имеет тип указателя на массив [n] для const int, когда вы передаете параметр указателя типа на массив [n] из int. Оба типа несовместимы.

Как применить константную корректность к указателям массива, переданным в качестве параметров? Возможно ли это?

Это невозможно. Нет никакого способа сделать это в стандартном C без использования явного приведения.

Но GCC разрешает это как расширение :

В GNU C указатели на массивы с квалификаторами работают аналогично указателям на другие квалифицированные типы. Например, значение типа int (*)[5] может использоваться для инициализации переменной типа const int (*)[5]. Эти типы несовместимы в ISO C, потому что спецификатор const формально привязан к типу элемента массива, а не самому массиву.

 extern void
 transpose (int N, int M, double out[M][N], const double in[N][M]);
 double x[3][2];
 double y[2][3];
 ...
 transpose(3, 2, y, x); 

Дальнейшее чтение: Указатель на массив с квалификатором const в C и С++

Ответ 3

OP описывает функцию func(), которая имеет следующую подпись.

void func(size_t n, const int (*arr)[n])

OP хочет называть его передачей различных массивов

#define SZ(a) (sizeof(a)/sizeof(a[0]))

int array1[3];
func(SZ(array1), &array1);  // problem

const int array2[3] = {1, 2, 3};
func(SZ(array2), &array2);

Как применить константную корректность к указателям массива, переданным в качестве параметров?

С помощью C11 используйте _Generic для выполнения кастинга по мере необходимости. Приведение происходит только тогда, когда вход имеет приемлемый тип не const, тем самым сохраняя безопасность типа. Это "как" это сделать. OP может считать его "раздутым", поскольку он сродни этому. Этот подход упрощает вызов макро/функции только одному параметру.

void func(size_t n, const int (*arr)[n]) {
  printf("sz:%zu (*arr)[0]:%d\n", n, (*arr)[0]);
}

#define funcCC(x) func(sizeof(*x)/sizeof((*x)[0]), \
  _Generic(x, \
  const int(*)[sizeof(*x)/sizeof((*x)[0])] : x, \
        int(*)[sizeof(*x)/sizeof((*x)[0])] : (const int(*)[sizeof(*x)/sizeof((*x)[0])])x \
  ))

int main(void) {
  #define SZ(a) (sizeof(a)/sizeof(a[0]))

  int array1[3];
  array1[0] = 42;
  // func(SZ(array1), &array1);

  const int array2[4] = {1, 2, 3, 4};
  func(SZ(array2), &array2);

  // Notice only 1 parameter to the macro/function call
  funcCC(&array1);  
  funcCC(&array2);

  return 0;
}

Выход

sz:4 (*arr)[0]:1
sz:3 (*arr)[0]:42
sz:4 (*arr)[0]:1

В качестве альтернативы код может использовать

#define funcCC2(x) func(sizeof(x)/sizeof((x)[0]), \
    _Generic(&x, \
    const int(*)[sizeof(x)/sizeof((x)[0])] : &x, \
          int(*)[sizeof(x)/sizeof((x)[0])] : (const int(*)[sizeof(x)/sizeof((x)[0])])&x \
    ))

funcCC2(array1);
funcCC2(array2);