Какая часть стандарта C позволяет компилировать этот код?

Я dynscat() некоторый код, и компилятор предупредил (законно), что функция 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 подавляет предупреждение компилятора для этого fragmentа кода. dynscat() функции dynscat() вообще отсутствовало; снова, добавив, что это подавляет предупреждение.

С показанным fragmentом кода ясно, что используется только адрес переменной, поэтому имеет смысл на одном уровне, что не имеет значения, что детали структуры неизвестны. Были переменная 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 тоже будет)? Какой раздел? Или я просто ударил по корпусу с нечетным шаром, который работает на всех машинах, к которым он портирован, но официально не санкционирован стандартом?

Похож на случай адреса адреса с неполным типом.

Использование указателей для неполных типов является абсолютно нормальным, и вы делаете это каждый раз, когда используете указатель-на-void (но никто никогда не говорил вам 🙂

Другое дело, если вы заявляете что-то вроде

 extern char a[]; 

Неудивительно, что вы можете назначить элементы a , правильно? Тем не менее, это неполный тип, и компиляторы сообщают вам, как только вы сделаете такой идентификатор операндом sizeof .

То, что у вас есть, является неполным (ISO / IEC 9899: 1999 и 2011 гг. – все эти ссылки одинаковы для обоих – §6.2.5 ¶22):

Структура или тип объединения неизвестного содержимого (как описано в п. 6.7.2.3) является неполным типом.

Недопустимый тип может быть lvalue:

§6.3.2.1 ¶1 (Lvalues, массивы и указатели функций)

Lvalue – это выражение с типом объекта или неполным типом, отличным от void; …

Таким образом, это похоже на любой другой унарный & с lvalue.

ваша линия

 extern struct t_dynstr qqparse_val; 

Является внешним объявлением объекта, а не определением. В качестве внешнего объекта у него «есть связь», а именно внешняя связь.

В стандарте говорится:

Если идентификатор объекта объявлен без привязки, тип объекта должен быть завершен до конца его декларатора, …

это означает, что если у него есть связь, тип может быть неполным. Таким образом, нет никаких проблем при выполнении &qqparse_val впоследствии. То, что вы не смогли бы сделать, будет sizeof(qqparse_val) поскольку тип объекта является неполным.

Декларация необходима, чтобы «ссылаться» на что-то. Необходимо определение, чтобы «использовать» что-то. Декларация может содержать некоторое ограниченное определение, как в «int a []»; Что пень меня:

 int f(struct _s {int a; int b;} *sp) { sp->a = 1; } 

gcc предупреждает, что ‘struct _s’ объявляется внутри списка параметров. И заявляет, что «его объем является ТОЛЬКО этим определением или декларацией …». Однако это не приводит к ошибке «sp-> a», которая не находится в списке параметров. При написании парсера «C» мне пришлось решать, где заканчивается область определения.

Ориентация на первую строку:

 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», что является бессмыслицей. Но это не то, что это значит. qqparse_val применяется к qqparse_val несмотря на их географическое разделение.

Вот мои мысли относительно стандарта (C11).

Раздел 6.5.3.2. Операторы адреса и косвенности

Ограничения

Параграф 1 : операнд унарного оператора & должен быть либо обозначением функции, результатом оператора [], либо унарного *, либо значением l, которое обозначает объект, который не является битовым полем и не объявлен в хранилище регистра спецификатор classа.

Пункт 2 : Операнд унарного * оператора должен иметь тип указателя.

Здесь мы не указываем никаких требований к объекту, кроме того, что это объект (а не битовое поле или регистр).

С другой стороны, давайте посмотрим на sizeof.

6.5.3.4 Операторы sizeof и _Alignof

Ограничения

Параграф 1: Оператор sizeof не применяется к выражению, имеющему тип функции или неполный тип, к имени в скобках такого типа или к выражению, которое обозначает член битового поля. Оператор _Alignof не применяется к типу функции или к неполному типу.

Здесь стандарт явно требует, чтобы объект не был неполным.

Поэтому я считаю, что это случай того, что явно не разрешено.

  • Плакат с 8 этапами перевода на языке C
  • Разница между int и char в getchar / fgetc и putchar / fputc?
  • Как указатель на указатели работает в C?
  • странный вывод в сравнении float с float literal
  • undefined символ для архитектуры x86_64 при компиляции программы C
  • Как получить длину функции в байтах?
  • Почему я не могу назначить массивы как & a = & b?
  • Замена fflush (stdin)
  • Является ли модификация строковых литералов неопределенным поведением в соответствии со стандартом C89?
  • atoi - как определить разницу между нулем и ошибкой?
  • Правильный способ передачи 2-мерного массива в функцию
  • Давайте будем гением компьютера.