Корректность констант для указателей массива?
Кто-то сделал аргумент, говорящий, что в современном 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);