Const и typedef массивов в C
В C возможно typedef
массив, используя эту конструкцию:
typedef int table_t[N];
Здесь table_t
теперь определяется как массив из N int
. Любая переменная, объявленная как table_t t;
, теперь будет вести себя как обычный массив int
.
Точка такой конструкции должна использоваться как тип аргумента в функции, например:
int doSomething(table_t t);
Возможно, был прототип относительно эквивалентной функции:
int doSomething(int* t);
Достоинство первой конструкции состоит в том, что она обеспечивает N
как размер таблицы. Во многих случаях безопаснее применять это свойство, а не полагаться на программиста, чтобы правильно определить это условие.
Теперь все хорошо, кроме этого, чтобы гарантировать, что содержимое таблицы не будет изменено, необходимо использовать квалификатор const
.
Следующее утверждение относительно просто понять:
int doSomething(const int* t);
Теперь doSomething
гарантирует, что он не будет изменять содержимое таблицы, переданной как указатель.
Итак, как насчет этой почти эквивалентной конструкции?
int doSomething(const table_t t);
Что здесь const
? содержимое таблицы или указатель на таблицу?
Если это указатель const
, существует ли другой способ (совместимый с C90), чтобы сохранить возможность определять размер таблицы и сообщить, что ее содержимое будет const?
Обратите внимание, что иногда необходимо изменить содержимое таблицы, поэтому свойство const
не может быть встроено в определение typedef.
[Edit] Спасибо за отличные ответы, полученные до сих пор.
Подводя итог:
- Исходное предположение о принудительном применении размера typedef N было совершенно неверным. Он в основном ведет себя так же, как обычный указатель.
- Свойство
const
также будет вести себя так же, как если бы оно было указателем (резко контрастирует с typedef с типом указателя, как подчеркивается @random ниже)
- Чтобы обеспечить размер (который не был начальным вопросом, но в конечном итоге сейчас очень важно...), см. ответ Джонатана
Ответы
Ответ 1
Содержимое таблицы будет постоянным. Легко проверяется с помощью этого кода.
#include<stdio.h>
typedef int table_t[3];
void doSomething(const table_t t)
{
t++; //No error, it a non-const pointer.
t[1]=3; //Error, it a pointer to const.
}
int main()
{
table_t t={1,2,3};
printf("%d %d %d %ld",t[0],t[1],t[2],sizeof(t));
t[1]=5;
doSomething(t);
return 0;
}
Ответ 2
Во-первых, вы ошибаетесь, прототипы функций
int doSomething(table_t t);
int doSomething(int* t);
в точности эквивалентны. Для параметров функции первое измерение массива всегда переписывается как указатель. Таким образом, нет гарантии размера полученного массива.
const
-qualification на массивах всегда применяется к базовому типу массива, поэтому два объявления
const table_t a;
int const a[N];
эквивалентны, а для параметров функций имеем
int doSomething(const table_t t);
int doSomething(int const* t);
Ответ 3
Достоинство первой конструкции состоит в том, что она обеспечивает N как размер таблицы.
Я не уверен, что вы имеете в виду здесь. В каких контекстах он "принудил" его? Если вы объявите функцию как
int doSomething(table_t t);
размер массива не будет соблюден. Я заказываю для обеспечения размера, вам придется идти по другому маршруту
int doSomething(table_t *t); // equivalent to 'int (*t)[N]'
Что здесь const?
Что касается const
... Когда const
применяется к типу массива, он полностью "падает" до элементов массива. Это означает, что const table_t
- это массив констант int
s, т.е. Он эквивалентен типу const int [N]
. Конечным результатом этого является то, что массив становится немодифицируемым. В контексте объявления параметра функции const table_t
будет преобразовано в const int *
.
Однако обратите внимание на одну особенную деталь, которая в этом случае не сразу очевидна: сам тип массива остается неконстантным. Это отдельные элементы, которые становятся const
. На самом деле невозможно const-qualify сам тип массива в C. Любые попытки сделать это заставят const-qualification "просеивать" отдельные элементы.
Эта особенность приводит к довольно неприятным последствиям в const-правильности массива. Например, этот код не будет компилироваться в C
table_t t;
const table_t *pt = &t;
несмотря на то, что он выглядит невинно с точки зрения const-correctness и будет компилироваться для любого типа объектов без массива. Язык С++ обновил свои правила корректности для решения этой проблемы, а C продолжает придерживаться своих старых способов.
Ответ 4
Типы массивов и типы указателей не эквивалентны на 100%, даже в этом контексте, когда вы в конечном итоге получаете тип указателя для параметра функции. Ваша ошибка заключается в том, что const
будет действовать одинаково, если бы это был тип указателя.
Чтобы расширить пример ARBY:
typedef int table_t[3];
typedef int *pointer_t;
void doSomething(const table_t t)
{
t++; //No error, it a non-const pointer.
t[1]=3; //Error, it a pointer to const.
}
void doSomethingElse(const pointer_t t)
{
t++; //Error, it a const pointer.
t[1]=3; //No error, it pointer to plain int
}
Он действует аналогично const int *
, но const pointer_t
вместо этого эквивалентен int * const
.
(Также, отказ от ответственности, пользовательские имена, заканчивающиеся на _t, не разрешены POSIX, они зарезервированы для будущего расширения)
Ответ 5
В стандарте 6.7.6.3 говорится:
Объявление параметра как '' массива типа должно быть скорректировано на '' квалифицированный указатель на тип
Это означает, что при объявлении параметра функции в качестве типа массива const int
он распадается на указатель на const int
(первый элемент в массиве). Эквивалентно const int*
в этом случае.
Также обратите внимание, что из-за вышеупомянутого правила размер массива не добавляет дополнительной безопасности! Это один большой недостаток на языке C, но это как.
Тем не менее, хорошей практикой является объявление массива с фиксированной шириной, как и у вас, поскольку статические анализаторы или умные компиляторы могут создавать диагностику для разных типов.