Эквивалентность p [0] и * p для неполных типов массивов
Рассмотрим следующий код (это произошло в результате этого обсуждения):
#include <stdio.h>
void foo(int (*p)[]) { // Argument has incomplete array type
printf("%d\n", (*p)[1]);
printf("%d\n", 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 есть ошибка.
Я бы поместил свои деньги (1).
Вопрос: Может ли кто-нибудь описать это поведение?
Разъяснение: Я знаю, что это можно "решить", указав размер массива в определении функции. Это не то, что меня интересует.
Для "бонусных" баллов: Может ли кто-нибудь подтвердить, что MSVC 2010 ошибочен, когда он отклоняет строку 10 со следующим сообщением?
1><snip>\prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]'
Ответы
Ответ 1
Раздел 6.5.2.1 из n1570, подстрока массива:
Ограничения
Одно из выражений должно иметь указатель типа '' для завершения типа объекта, а другой выражение должно иметь целочисленный тип, а результат имеет тип type.
Таким образом, стандарт запрещает выражение p[0]
, если p
является указателем на неполный тип. Нет такого ограничения для оператора косвенности *
.
Однако в старых версиях/черновиках стандарта (n1256 и C99) слово "полный" отсутствует в этом параграфе. Не будучи вовлеченным каким-либо образом в стандартную процедуру, я могу только догадываться, является ли это изменением или исправлением упущения. Поведение компилятора предлагает последнее. Это подкрепляется тем фактом, что p[i]
соответствует стандарту, идентичному *(p + i)
, и последнее выражение не имеет смысла для указателя на неполный тип, поэтому для p[0]
работать, если p
является указателем к неполному типу, необходим явный частный случай.
Ответ 2
Мой C немного ржавый, но я читаю, что когда у вас есть int (*p)[]
this:
(*p)[n]
Говорит "разыменование p
, чтобы получить массив из int, а затем взять n-й". Который, естественно, должен быть четко определен. В то время как это:
p[n][m]
Говорит: "Возьмите n-й массив в p, затем возьмите m-й элемент этого массива". Который не кажется полностью определенным; вы должны знать, насколько велики массивы, чтобы найти, где начинается n-й.
Это может работать для конкретного частного случая, где n = 0, потому что 0-й массив легко найти независимо от того, насколько велики массивы. Вы просто обнаружили, что GCC не признает этот особый случай. Я не знаю спецификацию языка в деталях, поэтому я не знаю, является ли это "ошибкой" или нет, но мои личные вкусы в дизайне языка заключаются в том, что p[n][m]
должен либо работать, либо не работать, а не работать, если n
статически известно как 0, а не иначе.
Является ли *p <===> p[0]
действительно окончательное правило из спецификации языка или просто наблюдением? Я не думаю о разыменовании и индексировании по нуле как о той же операции, когда я программирую.
Ответ 3
Для вашего вопроса "бонусные очки" (вероятно, вы должны были задать это как отдельный вопрос), MSVC10 ошибочен. Обратите внимание, что MSVC реализует только C89, поэтому я использовал этот стандарт.
Для вызова функции C89 §3.3.2.2 сообщает нам:
Каждый аргумент должен иметь такой тип, чтобы его значение можно было присвоить объект с неквалифицированной версией типа его соответствующий параметр.
Ограничения для назначения приведены в C89 §3.3.16:
Должно быть одно из следующих:... оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов, а также тип на которые указывают левые, есть все определители типа, указывающие на справа;
Таким образом, мы можем назначить два указателя (и, таким образом, вызвать функцию с параметром указателя с использованием аргумента указателя), если два указателя указывают на совместимые типы.
Совместимость различных типов массивов определяется в C89 §3.5.4.2:
Для двух типов массивов, которые должны быть совместимы, оба должны иметь совместимость типы элементов, и если присутствуют оба спецификатора размера, они должны имеют одинаковое значение.
Для двух типов массивов int []
и int [3]
это условие явно выполняется. Поэтому вызов функции является законным.
Ответ 4
void foo(int (*p)[])
{
printf("%d\n", (*p)[1]);
printf("%d\n", p[0][1]); // Line 5
}
Здесь p
является указателем на массив неопределенного числа int
s. *p
обращается к этому массиву, поэтому (*p)[1]
является вторым элементом массива.
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 С++ (и другие компиляторы) могут предупреждать о вещах, которые, по их мнению, не являются хорошей практикой, вещи, которые были найдены эмпирически часто ошибочными, или то, что писатели-компиляторы просто имели иррациональное недоверие, даже если они полностью соответствуют стандарту... Примеры, которые могут быть знакомы, включают "сравнение подписанных и неподписанных" и "присваивание в условном выражении (предлагать окружение дополнительными скобками)"