Эквивалентность p[0] и *p для неполных типов массивов


Рассмотрим следующий код (он появился в результате этого обсуждения):

#include <stdio.h>

void foo(int (*p)[]) {          // Argument has incomplete array type
    printf("%dn", (*p)[1]);
    printf("%dn", p[0][1]);    // Line 5
}

int main(void) {
    int a[] = { 5, 6, 7 };
    foo(&a);                    // Line 10
}

GCC 4.3.4 жалуется с сообщением об ошибке:

prog.c: In function ‘foo’:
prog.c:5: error: invalid use of array with unspecified bounds

То же сообщение об ошибке в GCC 4.1.2 и, по-видимому, инвариантно -std=c99, -Wall, -Wextra.

Таким образом, он недоволен выражением p[0], но он доволен *p, хотя они должны (теоретически) быть эквивалентными. Если я закомментирую строку 5, код компилируется и делает то, что я "ожидал" (отображает 6).

Предположительно верно одно из следующих утверждений:

    Мое понимание стандарта(ов) C неверно, и эти выражения не эквивалентны.
  1. GCC имеет ошибку.
Я бы поставил свои деньги на (1).

Вопрос: может ли кто-нибудь подробнее остановиться на этом поведении?

Уточнение: я знаю, что это можно "решить", указав размер массива в определении функции. Это не то, что меня интересует. в.


Для "бонусных" баллов: может ли кто-нибудь подтвердить, что MSVC 2010 находится в ошибке, когда он отклоняет строку 10 со следующим сообщением?

1><snip>prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]'
4 19

4 ответа:

Раздел 6.5.2.1 n1570, подписка на массив:

Ограничения

Одно из выражений должно иметь тип ‘указатель на полный тип объекта’, другое - "указатель на полный тип объекта". выражение должно иметь целочисленный тип, а результат-тип ‘type’.

Таким образом, стандарт запрещает выражение p[0], если p является указателем на неполный тип. Для оператора косвенности * такого ограничения не существует.

В более старых версиях / проектах стандарта, однако (n1256 и C99) слово "полный" в этом пункте отсутствует. Не будучи каким-либо образом вовлечен в стандартную процедуру, я могу только догадываться, является ли это прорывным изменением или исправлением упущения. Поведение компилятора предполагает последнее. Это подкрепляется тем фактом, что p[i] по стандарту идентичен *(p + i) и последнее выражение не имеет смысла для указателя на неполный тип, поэтому для p[0] работать, если p является указателем на неполный тип, то потребуется явно выраженный частный случай.

Мой C немного ржавый, но я читаю, что когда у вас есть int (*p)[] это:

(*p)[n]

Говорит "разыменование p, чтобы получить массив ints, затем возьмите N-й". Что, естественно, хорошо определено. Тогда как это:

p[n][m]

Говорит: "Возьмите N-й массив в p, затем возьмите M-й элемент этого массива". Который вообще не кажется четко определенным; вы должны знать, насколько велики массивы, чтобы найти, где начинается N-й.

Это может работать для конкретного специального случай, когда n = 0, потому что 0-й массив легко найти независимо от того, насколько велики массивы. Вы просто обнаружили, что GCC не признает этот особый случай. Я не знаю спецификацию языка в деталях, поэтому я не знаю, является ли это "ошибкой" или нет, но мои личные вкусы в языковом дизайне таковы, что p[n][m] должен работать или нет, а не то, что он должен работать, когда n статически известно, что 0, а не иначе.

Является ли *p <===> p[0] действительно определенным правилом из спецификации языка, или просто наблюдение? Я не думаю о разыменовании и индексации на ноль как об одной и той же операции, когда я программирую.

Для вашего вопроса "бонусные баллы" (вы, вероятно, должны были задать его как отдельный вопрос), MSVC10 находится в ошибке. Обратите внимание, что MSVC реализует только C89, поэтому я использовал этот стандарт.

Для вызова функции C89 §3.3.2.2 говорит нам:

Каждый аргумент должен иметь такой тип, что его значение может быть присвоено объект с безусловной версией типа его соответствующий параметр.

Ограничения для присвоения находятся в C89 §3.3.16:

Выполняется одно из следующих условий:... оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов, а также тип указанная слева имеет все квалификаторы указанного типа по праву;

Таким образом, мы можем назначить два указателя (и, таким образом, вызвать функцию с параметром указателя, используя аргумент указателя), если два указателя указывают на совместимые типы.

Совместимость различных типов массивов определяется в C89 §3.5.4.2:

Чтобы два типа массивов были совместимы, оба должны иметь совместимость типы элементов, и если присутствуют оба спецификатора размера, то они должны имеют одинаковое значение.

Для двух типов массивов int [] и int [3] это условие явно выполняется. Поэтому вызов функции является законным.

void foo(int (*p)[])
{
    printf("%d\n", (*p)[1]);
    printf("%d\n", p[0][1]);    // Line 5
}

Здесь p является указателем на массив неопределенного числа int s. *p обращается к этому массиву, поэтому (*p)[1] является 2-м элементом в массиве.

p[n] добавляет p и n раз больше размера массива, на который указывают, что неизвестно. Еще до рассмотрения [1], он сломан. Это правда, что ноль раз все равно 0, но компилятор, очевидно, проверяет правильность всех терминов без короткого замыкания, как только он видит ноль. Так...

Итак, это недоволен выражением p[0], но доволен *p, хотя они (теоретически) должны быть эквивалентны.

Как уже объяснялось, они явно не эквивалентны... думайте о p[0] как о p + 0 * sizeof *p, и это очевидно, почему....

Для" бонусных " баллов: может ли кто-нибудь подтвердить, что MSVC 2010 находится в ошибке, когда он отклоняет строку 10 со следующим сообщением? 1> \ prog.c (10): предупреждение C4048: различные индексы массива: 'int ()[]' и ' int ()[3]'

Visual C++ (и другие компиляторы) могут свободно предупреждать о вещах, которые, по их мнению, не являются хорошей практикой, вещах, которые были найдены эмпирически часто ошибочными, или вещах, к которым авторы компиляторов просто имели иррациональное недоверие, даже если они полностью легальны по стандарту.... Примеры, которые могут быть знакомы, включают "сравнение подписанного и неподписанного" и "присвоение в пределах условного (предложите окружить дополнительными скобками)"