Какие части C являются наиболее переносимыми? - PullRequest
15 голосов
/ 15 февраля 2011

Я недавно прочитал интервью с соавторами Lua Луисом Х. де Фигередо и Роберто Иерусалимши, где они обсуждали дизайн и реализацию Lua.Это было очень интригующе по меньшей мере.Однако, одна часть обсуждения подняла что-то в моей голове.Роберто говорил о Lua как о « автономном приложении » (то есть, это чистый ANSI C, который не использует ничего из ОС.) Он сказал, что ядро ​​Lua было полностью переносимым, и из-за его purity удалось перенести гораздо проще и на платформы, которые даже не рассматривались (например, роботы и встроенные устройства).

Теперь меня удивляет.C вообще очень переносимый язык.Итак, какие части C (а именно те из стандартной библиотеки) являются наиболее непереносимыми?и какие из них могут работать на большинстве платформ?Следует ли использовать только ограниченный набор типов данных (например, избегать short и, возможно, float)?А как насчет FILE и stdio системы?malloc и free?Кажется, что Луа избегает всего этого.Это доводит дело до крайности?Или они - корень проблем переносимости?Помимо этого, что еще можно сделать, чтобы сделать код чрезвычайно переносимым?

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

В качестве последнего замечания, все это обсуждение относится только к C89.

Ответы [ 6 ]

13 голосов
/ 15 февраля 2011

В случае с Lua у нас нет особых претензий к самому языку C, но мы обнаружили, что стандартная библиотека C содержит много функций, которые кажутся безвредными и простыми в использовании, пока вы не решите, что они это делают.не проверять их ввод на достоверность (что хорошо, если неудобно).Стандарт C говорит, что обработка некорректного ввода - это неопределенное поведение, позволяющее этим функциям делать все, что им захочется, даже вызывать сбой программы хоста.Рассмотрим, например, strftime.Некоторые libc просто игнорируют неверные спецификаторы формата, но другие libc (например, в Windows) вылетают!Теперь strftime не является ключевой функцией.Зачем падать, а не делать что-то разумное?Итак, Lua должен выполнить собственную проверку ввода перед вызовом strftime, и экспорт strftime в программы Lua становится рутиной.Следовательно, мы старались держаться подальше от этих проблем в ядре Lua, стремясь к автономности для ядра.Но стандартные библиотеки Lua не могут этого сделать, поскольку их цель - экспортировать средства в программы Lua, включая то, что доступно в стандартной библиотеке C.

9 голосов
/ 15 февраля 2011

«Отдельно стоящий» имеет особое значение в контексте языка C. Грубо говоря, отдельно стоящие хосты не обязаны предоставлять какие-либо стандартные библиотеки, включая библиотечные функции malloc / free, printf и т. Д. Некоторые Стандартные заголовки все еще требуются, но они определяют только типы и макросы (например, stddef.h).

6 голосов
/ 15 февраля 2011

C89 допускает два типа компиляторов: hosted и отдельно .Основное отличие состоит в том, что размещенный компилятор предоставляет всю библиотеку C89, в то время как автономный компилятор должен предоставлять только <float.h>, <limits.h>, <stdarg.h> и <stddef.h>.Если вы ограничитесь этими заголовками, ваш код будет переносим на любой C89 компилятор.

4 голосов
/ 15 февраля 2011

Это очень широкий вопрос.Я не собираюсь давать конкретный ответ, вместо этого я подниму некоторые вопросы.

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

  • Размер слова .sizeof(long) может быть четыре байта на одной платформе, восемь на другой.Размеры short, int, long и т. Д. Имеют минимальный размер (часто относительно друг друга), но в противном случае нет никаких гарантий.
  • Порядковый номер .int a = 0xff00; int b = ((char *)&a)[0]; может назначить 0 на b на одной платформе, -1 на другой.
  • Кодировка символов .\0 всегда является нулевым байтом, но то, как отображаются другие символы, зависит от ОС и других факторов.
  • I / O в текстовом режиме .putchar('\n') может выдавать символ перевода строки на одной платформе, возврат каретки на следующей и комбинацию каждого на другой.
  • Подпись символа .char может принимать или не принимать отрицательные значения.
  • Размер байта .В то время как в настоящее время байт составляет восемь битов практически везде, C обслуживает даже несколько экзотических платформ, где его нет.

Различные размеры слова и порядковые номера являются общими.Проблемы кодировки символов могут возникать в любом приложении для обработки текста.Машины с 9-битными байтами чаще всего можно найти в музеях.Это ни в коем случае не исчерпывающий список.

(И, пожалуйста, не пишите C89, это устаревший стандарт. C99 добавил довольно полезные вещи для переносимости, такие как целые числа фиксированной ширины int32_t и т. Д.)

2 голосов
/ 19 июня 2015

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

Любой, кто пишет код для C, должен решить (намеренно или по умолчанию), какие размеры int он будет поддерживать;в то время как можно написать код на C, который будет работать с любым допустимым размером int, это требует значительных усилий, и результирующий код часто будет гораздо менее читаемым, чем код, предназначенный для определенного целочисленного размера.Например, если у вас есть переменная x типа uint32_t, и вы хотите умножить ее на другую y, вычисляя мод результата 4294967296, оператор x*=y; будет работать на платформах, где int равен 32бит или меньше, или где int равно 65 битам или больше, но будет вызывать Undefined Behavior в случаях, когда int составляет от 33 до 64 бит, и произведение, если операнды рассматривались как целые числа, а не как членыалгебраическое кольцо, которое оборачивает мод 4294967296, будет превышать INT_MAX.Можно заставить оператор работать независимо от размера int, переписав его как x*=1u*y;, но это сделает код менее понятным, и случайное исключение 1u* из одного из умножений может иметь катастрофические последствия.

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

2 голосов
/ 15 февраля 2011

Все, что является частью стандарта C89, должно переноситься на любой компилятор, соответствующий этому стандарту.Если вы будете использовать чистый C89, вы сможете легко его портировать.Любые проблемы с переносимостью могут быть вызваны ошибками компилятора или местами, в которых код вызывает поведение, зависящее от реализации.

...