Техническая законность несовместимых заданий указателя

В стандарте C11 ISO/IEC 9899: 2011 (E) указаны следующие ограничения для простых назначений в п. 6.5.16.1/1:

Одно из следующего:

  • левый операнд имеет атомный, квалифицированный или неквалифицированный арифметический тип, а правый имеет арифметический тип;
  • левый операнд имеет атомную, квалифицированную или неквалифицированную версию структуры или объединения тип, совместимый с типом права;
  • левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя и (учитывая тип, который будет иметь левый операнд после преобразования lvalue) оба операнда указатели на квалифицированные или неквалифицированные версии совместимых типов, а тип слева - все определители типа, на которые указывает справа;
  • левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя и (учитывая тип, который будет иметь левый операнд после преобразования lvalue), один операнд является указателем к типу объекта, а другой - указатель на квалифицированную или неквалифицированную версию void, а тип, на который указывает левый, имеет все квалификаторы типа, указывающего на по праву;
  • левый операнд является атомарным, квалифицированным или неквалифицированным указателем, а право - нулевым постоянная указателя; или
  • левый операнд имеет тип атомный, квалифицированный или неквалифицированный _Bool, а правый - указатель.

Меня интересует случай, когда обе стороны являются указателями на несовместимые типы, отличные от void. Если я правильно понимаю, это должно, по крайней мере, вызвать UB, поскольку оно нарушает это ограничение. Один пример для несовместимых типов должен быть (согласно §6.2.7 и §6.7.2) int и double.

Следовательно, следующая программа должна быть нарушена:

int main(void) {
  int a = 17;
  double* p;
  p = &a;
  (void)p;
}

Оба gcc и clang предупреждают о "-Wincompatible-pointer-types", но не прерывают компиляцию (компиляция с помощью -std=c11 -Wall -Wextra -pedantic).

Аналогично, следующая программа только приводит к предупреждению "-Wint-conversion", при компиляции просто отлично.

int main(void) {
  int a;
  double* p;
  p = a;
  (void)p;
}

Исходя из С++, я ожидал, что для любого из этих тестовых случаев потребуется компиляция. Есть ли причина, по которой любая из программ будет стандартно-правовой? Или существуют ли по крайней мере значительные исторические причины для поддержки этого стиля кода даже при отключении развлекательных расширений GNU C, явно используя -std=c11 вместо -std=gnu11?

Ответ 1

Флаг компилятора (как gcc, так и clang) для запроса проверок на соответствие строгим стандартам и отказа от компиляции несоответствующего кода -pedantic-errors:

$ gcc -std=c11 -pedantic-errors x.c
x.c: In function ‘main’:
x.c:3:15: error: initialization from incompatible pointer type [-Wincompatible-pointer-types]
   double* p = &a;
               ^

Clang:

$ clang -std=c11 -pedantic-errors x.c
x.c:3:11: error: incompatible pointer types initializing 'double *' with an
      expression of type 'int *' [-Werror,-Wincompatible-pointer-types]
  double* p = &a;
          ^   ~~
1 error generated.

Значительная доля (по меньшей мере) типичного кода C в дикой природе несоконформна, поэтому -pedantic-errors приведет к сбою большинства программ и библиотек C.

Ответ 2

Пример вашего кода и ваша ссылка на стандарт не совпадают. Примером является инициализация и 6.5.16 говорит о назначении.

Смутно требование соответствия типа находится в разделе 6.5.16 ограничений для назначения, но "только" в разделе семантики (6.7.9) для инициализации. Поэтому компиляторы имеют "право" не выдавать диагностику для инициализации.

В C нарушениям ограничений требуется только "диагностика", компилятор может продолжить компиляцию, но нет гарантии, что результирующий исполняемый файл будет действительным.

На моей платформе, тестирование Debian, оба компилятора дают мне диагностику без каких-либо опций, поэтому я предполагаю, что ваша установка должна быть довольно старой и устаревшей.

Ответ 3

Есть ли причина, по которой любая из программ будет стандартно-правовой?

Эти программы не являются "нормативными". Они содержат нарушения ограничений, и вы уже указали правильный текст из стандарта.

Компиляторы соответствуют стандарту, производя диагностику для нарушения ограничений. Стандарт не требует компиляции для прерывания в случае нарушения ограничения или другой ошибочной программы.

Он не говорит ни на сколько слов, но единственный разумный вывод заключается в том, что любой исполняемый файл, созданный в результате программы, содержащей нарушение ограничения, имеет полностью поведение undefined. (Я видел, что люди пытаются спорить иначе).

Следуют следующие предположения: C (и С++) используются для многих целей; иногда люди хотят "ассемблера высокого уровня" для своей машины и не заботятся о переносимости или стандартах. Предположительно, поставщики компиляторов устанавливают значения по умолчанию для того, что, по их мнению, предпочитает их целевая аудитория.

Ответ 4

Нет, Да


Это действительно очень просто.

Диаграмма Венна соответствующего кода

Нет, не стандарты законны. Да, существенные исторические причины.

С изначально даже не было бросков. Как язык системного программирования, использование его как ультрамощного прославленного ассемблера было не только разумным, но и лучшим практическим опытом и практикой - только в тот же день.

Ключевая часть информации должна освещать вещи: на самом деле задача компилятора не выполнять или не выполнять спецификацию. Спецификация, фактически, буквально, только предположение. Фактическое задание компилятора заключается в компиляции всего кода C, который когда-либо был написан, включая pre-C11, pre-C99, pre-C89 и даже pre-K & R. Вот почему существует так много дополнительных ограничений. Проекты с современными стилями кода включают строго соответствующие режимы.

Существует способ, которым C определяется в стандартах, и существует способ, которым C используется на практике. Итак, вы можете видеть, компилятор просто не может отказаться от создания кода.

В течение десятилетий разработчики переходили на портативный, строго соответствующий код, но когда C впервые появился, он был немного похож на действительно потрясающе мощный ассемблер, и это был своего рода открытый сезон по арифметике адреса и типу punning. Программы в те дни были в основном написаны для одной архитектуры за раз.