Какая часть стандарта C позволяет компилировать этот код?
Я исправлял некоторый код, и компилятор предупреждал (законно) о том, что функция dynscat()
не была объявлена - кто-то еще понимает приемлемый стандарт кодирования - поэтому я отслеживал, где функция определена (достаточно простая) и который заголовок объявил (нет, Grrr!). Но я ожидал найти детали определения структуры, необходимые для объявления extern
qqparse_val
:
extern struct t_dynstr qqparse_val;
extern void dynscat(struct t_dynstr *s, char *p);
extern void qqcat(char *s);
void qqcat(char *s)
{
dynscat(&qqparse_val, s);
if (*s == ',')
dynscat(&qqparse_val, "$");
}
Функция qqcat()
в исходном коде была статической; выражение extern подавляет предупреждение компилятора для этого фрагмента кода. Объявление функции dynscat()
вообще отсутствовало; снова, добавив, что он подавляет предупреждение.
С показанным фрагментом кода ясно, что используется только адрес переменной, поэтому имеет смысл на одном уровне, что не имеет значения, что детали структуры неизвестны. Если бы переменная extern struct t_dynstr *p_parseval;
, вы бы не увидели этого вопроса; что будет на 100% ожидаемым. Если код необходим для доступа к внутренним структурам структуры, то потребуется определение структуры. Но я всегда ожидал, что если вы заявите, что переменная была структурой (а не указателем на структуру), компилятор хотел бы знать размер структуры - но, по-видимому, нет.
Я пытался спровоцировать GCC на жалобы, но это не так, даже GCC 4.7.1:
gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c
Код компилируется в AIX, HP-UX, Solaris, Linux в течение десятилетия, поэтому он не является специфичным для GCC, что он принят.
Вопрос
Это разрешено стандартом C (прежде всего C99 или C11, но C89 тоже будет)? Какой раздел? Или я просто ударил по корпусу с нечетным шаром, который работает на всех машинах, к которым он портирован, но официально не санкционирован стандартом?
Ответы
Ответ 1
Похож на случай адреса адреса с неполным типом.
Использование указателей для неполных типов является абсолютно нормальным, и вы делаете это каждый раз, когда используете указатель-на-void (но никто никогда не говорил вам: -)
Другой случай: если вы объявите что-то вроде
extern char a[];
Неудивительно, что вы можете назначить элементы a
, правильно? Тем не менее, это неполный тип, и компиляторы сообщают вам, как только вы сделаете такой идентификатор операндом sizeof
.
Ответ 2
То, что у вас есть, является неполным (ISO/IEC 9899: 1999 и 2011 гг. - все эти ссылки одинаковы для обоих - §6.2.5 ¶22):
Структура или тип объединения неизвестного содержимого (как описано в п. 6.7.2.3) является неполной тип.
Недопустимый тип может быть lvalue:
§6.3.2.1 ¶1 (Lvalues, массивы и обозначения функций)
lvalue - это выражение с типом объекта или неполным типом, отличным от void;...
Таким образом, он как и любой другой унарный &
с lvalue.
Ответ 3
ваша линия
extern struct t_dynstr qqparse_val;
Является внешним объявлением объекта, а не определением. В качестве внешнего объекта у него "есть связь", а именно внешняя связь.
В стандарте говорится:
Если идентификатор объекта объявлен без привязки, тип объекта должен быть завершен до конца его декларатора,...
это означает, что если у него есть связь, тип может быть неполным. Поэтому нет проблем при выполнении &qqparse_val
. То, что вы не смогли бы сделать, было бы sizeof(qqparse_val)
, так как тип объекта является неполным.
Ответ 4
Декларация необходима, чтобы "ссылаться" на что-то.
Необходимо определение, чтобы "использовать" что-то.
Декларация может содержать некоторое ограниченное определение, как в "int a []";
Что меня преследует:
int f(struct _s {int a; int b;} *sp)
{
sp->a = 1;
}
gcc предупреждает, что 'struct _s' объявляется внутри списка параметров. И заявляет, что "его объем является ТОЛЬКО этим определением или декларацией...". Однако это не приводит к ошибке "sp- > a", которая не находится в списке параметров. При написании парсера "C" мне пришлось решать, где закончилась область определения.
Ответ 5
Фокусировка на первой строке:
extern struct t_dynstr qqparse_val;
Его можно разделить на отдельные этапы создания типа и переменной, в результате чего получается эквивалентная пара строк:
struct t_dynstr; /* declaration of an incomplete (opaque) struct type */
extern struct t_dynstr qqparse_val; /* declaration of an object of that type */
Вторая строка выглядит так же, как оригинал, но теперь она относится к типу, который уже существует из-за первой строки.
Первая строка работает, потому что это просто непрозрачные структуры.
Вторая строка работает, потому что вам не нужен полный тип для объявления extern.
Комбинация (вторая строка работает без первой строки) работает, потому что объединение объявления типа с объявлением переменной работает в целом. Все они используют тот же принцип:
struct { int x,y; } loc; /* define a nameless type and a variable of that type */
struct point { int x,y; } location; /* same but the type has a name */
union u { int i; float f; } u1, u2; /* one type named "union u", two variables */
Немного смешно, когда extern
следует за объявлением типа, например, может быть, вы пытаетесь сделать сам тип "extern", что является бессмыслицей. Но это не то, что это значит. extern
применяется к qqparse_val
, несмотря на их географическое разделение.
Ответ 6
Здесь мои мысли относительно стандарта (C11).
Раздел 6.5.3.2: Операторы адреса и косвенности
Ограничения
Параграф 1: операнд унарного и оператора должен быть либо обозначением функции, результатом оператора [], либо унарного *, либо значением l, которое обозначает объект, который не бит -field и не объявляется спецификатором класса хранения регистра.
Пункт 2: операнд унарного * оператора должен иметь тип указателя.
Здесь мы не указываем никаких требований к объекту, кроме того, что это объект (а не битовое поле или регистр).
С другой стороны, посмотрим на sizeof.
6.5.3.4 Операторы sizeof и _Alignof
Ограничения
Пункт 1: Оператор sizeof не должен применяться к выражению, имеющему тип функции или неполный тип, к имени в скобках такого типа или к выражению, которое обозначает бит- член поля. Оператор _Alignof не применяется к типу функции или к неполному типу.
Здесь стандарт явно требует, чтобы объект не был неполным.
Поэтому я считаю, что это случай того, что явно не разрешено.