Постоянные типы массивов в C, изъяны в стандарте?

Пункт 6.7.3.8 состояний спецификации C99

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

В rationale (логическая страница 87, физическая страница 94) пример приведения плоского указателя на (переменную длину) указатель массива.

void g(double *ap, int n)
{
    double (*a)[n] = (double (*)[n]) ap;
    /* ... */ a[1][2] /* ... */
}

Конечно, если массив ap не изменяется внутри функции, он должен быть отмечен как const, однако приведение в

void g(const double *ap, int n)
{
    const double (*a)[n] = (const double (*)[n]) ap;
    /* ... */
}

не сохраняет квалификатор const, поскольку (на 6.7.3.8) он применяется к элементам цели вместо самой цели, которая имеет тип массива double[n]. Это означает, что компиляторы будут правильно жаловаться, если будут указаны соответствующие флаги (-Wcast-qual для GCC). Невозможно обозначить тип массива const в C, но это приведение очень полезно и "правильно". Флаг -Wcast-qual полезен для идентификации неправильного использования параметров массива, но ложные срабатывания препятствуют его использованию. Обратите внимание, что индексирование a[i][j] является более читаемым и, со многими компиляторами, дает лучший машинный код, чем ap[i*n+j], поскольку первая позволяет вывести некоторую целочисленную арифметику из внутренних циклов с меньшим анализом.

Если компиляторы просто рассматривают это как особый случай, эффективно поднимая квалификаторы от элементов к типу массива, чтобы определить, удаляет ли данный бросок квалификаторы или должен ли спецификация быть изменена? Назначение не задано для типов массивов, поэтому было бы больно, чтобы квалификаторы всегда применялись к типу массива, а не только к элементам, в отличие от 6.7.3.8?

Ответы

Ответ 1

Это известная проблема, которая несколько раз обсуждалась за последние 10 лет на comp.std.c. Суть в том, что конкретный случай, который вы представили, в настоящее время не является законным в стандарте C; вам нужно либо удалить квалификатор, либо воздержаться от использования указателя на массив, чтобы ссылаться на квалифицированные элементы в массиве.

Если вы думаете, что у вас есть хорошая идея, чтобы преодолеть эту проблему, вы можете отправить ее в news:comp.std.c для обсуждения. Если другие согласны с тем, что это хорошая идея, вы или кто-то другой можете подать отчет о дефекте, чтобы изменить поведение (есть несколько членов комитета, которые часто бывают comp.std.c, поэтому отзывы людей, которые потенциально могли бы пересматривать DR, быть полезными, прежде чем подавать его). Я думаю, что могут возникнуть некоторые проблемы с вашим предложением о том, чтобы квалификаторы влияли на массив, но я должен был бы подумать о нем.

Ответ 2

Возможное обходное решение для программиста C (но не для компилятора):

gcc с -Wcast-qual не жалуется на это:

void g(const double *ap, int n)
{
    int i;
    struct box 
    {
      double a[n];
    };
    const struct box *s = (const struct box *)ap;

    for (i=0; i<n; ++i)
    {
       doStuffWith(s->a[i]);
       /* ... */
    }
}

Даже если это не очень элегантно. Элемент trailing array a также имеет немного другое значение между C89 и C99, но по крайней мере вы получаете предполагаемый эффект.

Ответ 3

Ситуация неудобна с указателями (т.е. массивами), но здесь мое воспоминание о деталях:

const double *ap - указатель на константу double;

double *const ap - постоянный указатель на double;

const double *const ap - постоянный указатель на константу double;

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

EDIT: этот ответ неверен для вопроса - я оставляю его, чтобы сохранить комментарии ниже, которые уточняют проблему для простых смертных (или ржавых разработчиков C...)