Подтвердить что ты не робот

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 ниже)
  • Чтобы обеспечить размер (который не был начальным вопросом, но в конечном итоге сейчас очень важно...), см. ответ Джонатана
4b9b3361

Ответ 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, но это как.

Тем не менее, хорошей практикой является объявление массива с фиксированной шириной, как и у вас, поскольку статические анализаторы или умные компиляторы могут создавать диагностику для разных типов.