Эквивалентность 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 неверно, и эти выражения не эквивалентны.
- GCC имеет ошибку.
Вопрос: может ли кто-нибудь подробнее остановиться на этом поведении?
Уточнение: я знаю, что это можно "решить", указав размер массива в определении функции. Это не то, что меня интересует. в.
Для "бонусных" баллов: может ли кто-нибудь подтвердить, что MSVC 2010 находится в ошибке, когда он отклоняет строку 10 со следующим сообщением?
1><snip>prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]'
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++ (и другие компиляторы) могут свободно предупреждать о вещах, которые, по их мнению, не являются хорошей практикой, вещах, которые были найдены эмпирически часто ошибочными, или вещах, к которым авторы компиляторов просто имели иррациональное недоверие, даже если они полностью легальны по стандарту.... Примеры, которые могут быть знакомы, включают "сравнение подписанного и неподписанного" и "присвоение в пределах условного (предложите окружить дополнительными скобками)"