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

Эквивалентность 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]'
4b9b3361

Ответ 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 С++ (и другие компиляторы) могут предупреждать о вещах, которые, по их мнению, не являются хорошей практикой, вещи, которые были найдены эмпирически часто ошибочными, или то, что писатели-компиляторы просто имели иррациональное недоверие, даже если они полностью соответствуют стандарту... Примеры, которые могут быть знакомы, включают "сравнение подписанных и неподписанных" и "присваивание в условном выражении (предлагать окружение дополнительными скобками)"