Действительные программы на C89, но не на C99

Есть ли функции/семантика, введенные или удаленные в C99, которые сделали бы хорошо определенную программу, написанную на C89, либо

  • недействителен (т.е. не компилируется больше, в соответствии со стандартом C99)
  • компиляция, но имеющая разную семантику.

Мои выводы до сих пор касались явно недействительных программ:

  • неявный int (C89 §3.5.2)
  • Объявление неявной функции (C89 §3.3.2.2)
  • не возвращается из функции, ожидающей возвращаемого значения (C89 §3.6.6.4)
  • с использованием новых ключевых слов в качестве идентификатора (например, restrict, inline и т.д.)
  • hacks с участием //, которые теперь рассматриваются как комментарии. Однако почти никогда не встречался в производственном коде.

Тонкие изменения, что делает тот же код с другой семантикой:

  • Целочисленное деление было четко определено, например, 3/2 теперь должно усекать в направлении нуля (C99 §6.5.5/6) вместо определения реализации (C89 §3.3.5/6)
  • strtod получила возможность анализировать шестнадцатеричные числа в C99, анализируя 0x или 0x

Что я пропустил?

Ответы

Ответ 1

Существует множество программ, которые считались бы действительными в соответствии с C89 до публикации C99, которые некоторые люди настаивают, что они никогда не были действительными. C89 содержит правило, которое требует, чтобы к объекту любого типа можно было получить доступ только с помощью указателя этого типа, связанного типа или типа символа. До публикации C99 это правило обычно интерпретировалось как применяемое только к "именованным" объектам (переменные статической или автоматической продолжительности, к которым обращаются напрямую по имени), и только в ситуациях, когда объект, о котором идет речь, не имел своего адреса взятых непосредственно перед тем, как он использовался как другой тип указателя. Такая интерпретация была обусловлена ​​рядом факторов:

  • Одна из заявленных целей Стандарта заключалась в том, чтобы соответствовать тем, что делали существующие компиляторы и программы, и хотя для существующих программ было бы редко обращаться к дискретным именованным переменным, используя указатели разных типов, кроме случаев где переменный адрес был взят непосредственно перед таким использованием, многие другие обычаи пейнинга типа указателя были довольно распространены.

  • Обоснование Стандарта включает в качестве единственного примера функцию, которая получает указатель одного примитивного типа для записи глобальной переменной другого примитивного типа таким образом, что у компилятора не будет особых причин ожидать сглаживания, Возможность сохранять глобальные переменные в регистрах явно полезная оптимизация, и заявленная цель этого правила заключается в том, чтобы разрешить такую ​​оптимизацию в тех случаях, когда у компилятора не было причин ожидать появления псевдонимов. Конструкции вне закона, такие как (int*)&foo=23;, ничего не помогают таким оптимизациям, поскольку тот факт, что код принимает адрес foo и разыменовывает его, должен сделать его совершенно ясным для любого компилятора, который не намеренно тупит, что код собирается изменить foo.

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

  • Если типы структуры V и W имеют общую начальную последовательность, U - любой тип объединения, содержащий оба, а p - это V*, который идентифицирует V внутри a U, то (W*)(U*)p может использоваться для доступа к этим общим членам и будет эквивалентен (W*)p. Если компилятор не мог показать, что p не может быть указателем на член какого-либо объединения, содержащего W, требуется, чтобы (W*)p имел доступ к общим членам; было бы более полезно просто рассматривать такой общий доступ к члену как законный, независимо от того, может ли существовать U, чем искать оправдания, чтобы отрицать его.

  • Ничто в правилах C89 не дает четкого представления о том, как определяется "тип" области выделенного хранилища или как хранилище, в котором хранятся вещи одного типа, которые больше не нужны, могут быть переназначены для хранения вещей другой.

  • Отслеживание регистров, назначенных именованным переменным, было проще, чем отслеживать регистры, выделенные для других исключений указателей, а код, который был заинтересован в минимизации количества загрузок и хранилищ с помощью указателей, часто копировал вещи в именованные переменные и работайте над ними.

В C99 добавлены правила "эффективного типа", которые явно применимы к выделенному хранилищу. Некоторые настаивают на том, что это просто "разъяснения" правил, которые уже существовали на C89, но по вышеуказанным причинам я считаю, что точка зрения несостоятельна. Модно утверждать, что единственными причинами, по которым компиляторы не применяли правила псевдонимов для неназванных объектов, являются # 5 и # 6, но возражения # 1- # 4 одинаково значимы (и продолжают применяться к C99 так же сильно, как C89). Тем не менее, поскольку C99 добавил эффективные правила типа, многие конструкции, которые считались бы законными большинством распространенных интерпретаций правил C89, явно запрещены.

Ответ 2

В качестве элемента сравнения и сравнения кодовая база git/git остается строго соответствующей C89 и не использует инициализаторы C99 или функции из более нового стандарта C.
Это подробно описано в Git 2.23 (Q3 2019) в Руководстве по кодированию Git.

Этот ответ иллюстрирует функцию после C89, которая может быть совместима с C89.

См. commit cc0c429 (16 июля 2019 г.) от Джунио С. Хамано (gitster).
(Merged by Junio C Hamano -- [TG41] -- in commit fe9dc6b, 25 Jul 2019)

CodingGuidelines: изложить правила пост-C89

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

Покажите их.

При этом немного расширите существующее правило объявления переменных до читайте лучше с недавно изложенным правилом для цикла for.

правила кодирования теперь включают в себя:

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

Из этого правила есть несколько исключений:

  • с начала 2012 года с e1327023ea (Git v1.7.9.2) мы используем определение enum, за последним элементом которого следует запятая.
    Это, подобно инициализатору массива, оканчивающемуся запятой, можно использовать для уменьшения шума патча при добавлении нового идентификатора в конце.

  • с середины 2017 года с cbc0f81d (Git v2.15.0-rc0) мы используем назначенные  инициализаторы для структуры (например, "struct t v = { .val = 'a' };")
    Существуют определенные функции C99, которые, возможно, было бы неплохо использовать в нашей базе кода, но мы колебались, чтобы избежать нарушения совместимости со старыми компиляторами.
    Но на самом деле мы не знаем, используют ли сейчас люди даже компиляторы до C99.
    Если этот патч может выдержать несколько выпусков без жалоб, то мы можем быть более уверены, что указанные инициализаторы широко поддерживаются нашей базой пользователей.
    Это также указывает на то, что могут поддерживаться другие функции C99, но не является гарантией (например, gcc назначил инициализаторы до появления C99).

  • с середины 2017 года с 512f41cf (Git v2.15.0-rc0) мы используем назначенные инициализаторы для массива (например, "int array[10] = { [5] = 2 }").
    Это еще один тестовый шарик, чтобы увидеть, если мы получаем жалобы от людей чьи компиляторы не поддерживают назначенный инициализатор для массивов.
    Раньше они были запрещены, но мы не слышали сообщений о поломках, и они считаются безопасными.

  • Переменные должны быть объявлены в начале блока, перед первым оператором (т.е. -Wdeclaration-after-statement).

  • Объявление переменной в цикле for "for (int i = 0; i < 10; i++)" все еще не разрешено в этой кодовой базе.